43 Commits

Author SHA1 Message Date
186cca3e27 Merge pull request 'v1.5 new shapes + self culling + caching + coding tools/cleanup' (#10) from dev into master
All checks were successful
deploy / deploy (push) Successful in 5m42s
Reviewed-on: #10
2024-03-14 22:30:29 +01:00
08bde119b2 updated version for publish 2024-03-14 22:28:45 +01:00
6922dcc9d9 improved naming + added new voxel list builder + Half Stairs Slab + Half Stairs Stair + Layer + coherence in model orientations 2024-03-14 22:05:14 +01:00
b1ba784ee3 made stairs connect when 2 different instance are used + organized todos 2024-03-10 15:43:06 +01:00
c207fb5ae6 renamed blocks for more clarity and expandability 2024-03-10 15:26:29 +01:00
5fcb983592 fixed small cube and caching for dynamic models items because items rerender often 2024-03-09 15:26:50 +01:00
bd412bfccd added missing shape 2024-03-08 23:50:50 +01:00
96f08bdd3e w.i.p more shapes + more coherent properties name (yes changing again) 2024-03-08 23:50:17 +01:00
394c75f9ed fix error + cleanup 2024-03-08 20:45:11 +01:00
e6c9ee63cd self culling !! 2024-03-08 20:38:14 +01:00
239802ce7b cleanup 2024-03-06 22:16:43 +01:00
5f87489939 better caching for both appearances and meshed models 2024-03-06 22:10:11 +01:00
d43d9456ee Merge pull request 'moved and created data generation for all elements other than models + removed block that aren't working' (#9) from dev into master
All checks were successful
deploy / deploy (push) Successful in 5m48s
Reviewed-on: #9
2024-03-05 21:56:03 +01:00
de7acfee50 moved and created data generation for all elements other than models + removed block that aren't working + cleanup 2024-03-05 21:54:36 +01:00
2957a899db Merge pull request 'Sound support + OffHand camo.' (#8) from dev into master
All checks were successful
deploy / deploy (push) Successful in 5m6s
Reviewed-on: #8
2024-03-04 17:04:13 +01:00
6ca266b676 patched sounds on the frames and made it so that it will take the texture of the block in offhand 2024-03-04 16:57:18 +01:00
Adrien1106
4d87ce9753 Merge pull request #1 from quentinlegot/CI-fix
Fix CI while fethcing wrapper-validation-action
2024-03-03 22:54:55 +01:00
e196975f1d Fix CI while fethcing wrapper-validation-action 2024-03-03 22:53:30 +01:00
e8ce990955 removed w.i.p. mixins 2024-03-03 21:57:22 +01:00
c3e2e626c8 removed useless element in github workflow 2024-03-03 21:49:09 +01:00
f3578a4da5 Merge pull request 'Indium suppor + github runner' (#7) from dev into master
Some checks failed
deploy / deploy (push) Failing after 5m3s
Reviewed-on: #7
2024-03-03 21:34:52 +01:00
dccc01ef49 Added Modrinth Auto Publisher on github workflow 2024-03-03 19:33:25 +01:00
961165104d added support for indium/sodium 2024-03-03 17:43:53 +01:00
28ae110e14 Merge pull request 'Double frames' (#6) from dev into master
All checks were successful
deploy / deploy (push) Successful in 5m14s
Reviewed-on: #6
2024-03-02 02:02:47 +01:00
13539fd91d added some todos 2024-03-02 01:57:25 +01:00
9c6e436fe3 fixed culling error and updated version to 1.3 2024-03-02 01:29:08 +01:00
53107d090b added double stair and found a culling error 2024-03-02 01:14:28 +01:00
a3ffe17a7e fixed connected texture (might still have bugs) 2024-03-01 23:43:28 +01:00
87616ae930 removed unused mixin 2024-03-01 21:58:57 +01:00
ce216d1f9f some rework / cleanup with full rework of culling to work by parts. no self culling yet? still missing work on connected textures 2024-03-01 21:56:22 +01:00
6a84a14f3e cleanup to prepare for double camos 2024-02-28 21:21:28 +01:00
fcd5d77b26 added item render support for athena blocks and some improved readability 2024-02-28 00:31:42 +01:00
0a050a2abc removed old changelog 2024-02-26 21:12:04 +01:00
5824d5d60c fixed chipped gradle import 2024-02-26 21:10:59 +01:00
6dc0cf7fdf Merge pull request 'Athena support + stair improvements + coloration' (#5) from dev into master
All checks were successful
deploy / deploy (push) Successful in 5m13s
Reviewed-on: #5
2024-02-26 17:05:24 +01:00
5779aa7bee added support for multilayered textures with different coloration's such as grass_block 2024-02-26 16:47:31 +01:00
58d9687ff8 added athena connected texture support (connected blocks do not refresh unless another block is placed next to it) 2024-02-26 16:28:02 +01:00
4ec8f4903a complexified stairs models for better transparent renders hopping to not deteriorate the performances too much 2024-02-26 16:09:19 +01:00
bba83206a2 fixed some shapes being badly generated 2024-02-26 15:25:52 +01:00
b30177a5c7 Merge pull request 'Fix mixin trying to inject code in server not available (because the class is client only)' (#3) from dev into master
All checks were successful
deploy / deploy (push) Successful in 4m57s
Reviewed-on: #3
2024-02-17 22:31:39 +01:00
aef1cfbf43 Fix mixin trying to inject code in server not available (because the class is client only) 2024-02-17 22:29:44 +01:00
1e132c71ef Merge pull request 'Add data generation before publishing' (#2) from dev into master
All checks were successful
deploy / deploy (push) Successful in 3m56s
Reviewed-on: #2
2024-02-16 22:29:53 +01:00
467b4f2031 Add data generation before publishing 2024-02-16 22:28:46 +01:00
212 changed files with 5692 additions and 5096 deletions

View File

@@ -28,6 +28,8 @@ jobs:
run: |
chmod +x ./gradlew
touch local.properties
- name: Generate data
run: ./gradlew runDatagen
- name: deploy
run: ./gradlew publish
- name: create tag

28
.github/workflows/publish.yml vendored Normal file
View File

@@ -0,0 +1,28 @@
name: Publish mod on Modrinth
on: [ push, workflow_dispatch ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: checkout repository
uses: actions/checkout@v3
- name: validate gradle wrapper
uses: gradle/wrapper-validation-action@v2
- name: setup jdk
uses: actions/setup-java@v3
with:
java-version: 17
distribution: 'oracle'
- name: make gradle wrapper executable
if: ${{ runner.os != 'Windows' }}
run: |
chmod +x ./gradlew
touch local.properties
- name: Generate data
run: ./gradlew runDatagen
- name: Publish Modrinth
run: ./gradlew modrinth
env:
MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }}

View File

@@ -1,55 +0,0 @@
Versions before 2.1.2 have been backfilled; I gotta be more on top of changelogs.
# next version (unreleased)
* Start sketching out a proper API, accessible through `TemplatesClientApi.getInstance()`.
* Not everything has been moved into the API package yet for ABI reasons.
* Code cleanups that hopefully didn't break ABI compat
* Remove some unused stuff from the jar
* Vertical slab placement no longer completely sucks
* Fix a bug where templates that look like blocks with randomized models, such as stone, could reroll their blockstate on every resource load.
* Forgot to specify a random seed.
* All templated blocks still use the *same* model, so templated stone will still not be randomly rotated/flipped, but at least it's now the *same* same model.
road map:
* want to fix texture orientation on vertical slabs/doors
* really want to fix the way vertical slabs place lmao (it's so buggy)
* clean up `StairShapeMaker`
# 2.1.1 (Aug 2, 2023)
Enable ambient-occlusion ("smooth lighting") on all Templates except for the slopes, which are still bugged
# 2.1.0 (Jul 31, 2023)
* Add a vertical slab template
* Add a "tiny slope" template
* Change the block entity NBT format to be much smaller
* Reduce memory footprint of the block entity
* Respect `doTileDrops`
* Improve creative ctrl-pick behavior on glowing Templates
* Adding a Barrier block to a Template makes it remove its model (not unbreakable)
# 2.0.4 (Jul 25, 2023)
* Apply more block tags
* Apply item tags
# 2.0.3 (Jul 23, 2023)
* add Door and Iron Door templates
* cool rivulet
# 2.0.2 (Jul 20, 2023)
* Add an Iron Trapdoor template
* Add some more mod metadata (change name to "Templates 2", add authors, fix sources link)
# 2.0.1 (Jul 11, 2023)
Fix a duplication glitch with the Stair Template, which was retaining its block entity after being broken.
# 2.0.0 (Jul 11, 2023)
Initial release

View File

@@ -3,6 +3,7 @@ import fr.altarik.CreateTag
plugins {
id "com.modrinth.minotaur" version "2.+"
id 'fabric-loom' version '1.5-SNAPSHOT'
id 'maven-publish'
}
@@ -66,6 +67,23 @@ repositories {
name 'altarik-releases'
url 'https://repo.altarik.fr/releases/'
}
maven {
url "https://maven.teamresourceful.com/repository/maven-public/"
}
maven {
url "https://maven.resourcefulbees.com/repository/maven-public/"
}
exclusiveContent {
forRepository {
maven {
name = "Modrinth"
url = "https://api.modrinth.com/maven"
}
}
filter {
includeGroup "maven.modrinth"
}
}
mavenCentral()
// Add repositories to retrieve artifacts from in here.
@@ -82,6 +100,20 @@ dependencies {
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
// Indium and sodium for sodium support
modCompileOnly "maven.modrinth:indium:${project.indium_version}+mc${project.minecraft_version}"
modCompileOnly "maven.modrinth:sodium:mc${project.minecraft_version}-${project.sodium_version}"
// modRuntimeOnly "maven.modrinth:indium:${project.indium_version}+mc${project.minecraft_version}"
// modRuntimeOnly "maven.modrinth:sodium:mc${project.minecraft_version}-${project.sodium_version}"
// Athena for connected texture
modCompileOnly "earth.terrarium.athena:athena-fabric-${project.minecraft_version}:${project.athena_version}"
modRuntimeOnly "earth.terrarium.athena:athena-fabric-${project.minecraft_version}:${project.athena_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.
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
}
@@ -91,6 +123,9 @@ processResources {
inputs.property "minecraft_version", project.minecraft_version
inputs.property "loader_version", project.loader_version
inputs.property "mod_id", project.mod_id
inputs.property "athena_version", project.athena_version
inputs.property "indium_version", project.indium_version
inputs.property "sodium_version", project.sodium_version
filteringCharset "UTF-8"
filesMatching("fabric.mod.json") {
@@ -156,6 +191,23 @@ publishing {
}
}
// configure modrinth publication
modrinth {
token = getEnv("MODRINTH_TOKEN", local.getProperty("modrinth_token"))
projectId = project.modrinth_id
versionNumber = project.mod_version
versionName = "${project.archives_base_name} ${project.mod_version}"
versionType = project.mod_version.endsWith('SNAPSHOT') ? 'beta' : 'release'
uploadFile = remapJar
gameVersions = [project.minecraft_version]
loaders = ["fabric"]
dependencies {
required.project "fabric-api"
optional.version "b1ZV3DIJ", "${project.athena_version}"
optional.version "Orvt0mRa", "${project.indium_version}+mc${project.minecraft_version}"
}
}
tasks.register("reportToDiscord", ReportDiscord) {
config.set(reportConfig)
}

View File

@@ -8,7 +8,8 @@ yarn_mappings=1.20.4+build.3
loader_version=0.15.6
# Mod Properties
mod_version = 1.0-SNAPSHOT
modrinth_id = jCpoCBpn
mod_version = 1.5
maven_group = fr.adrien1106
archives_base_name = ReFramed
mod_id = reframed
@@ -19,3 +20,7 @@ fabric_version=0.95.4+1.20.4
git_owner=Altarik
git_repo=ReFramed
athena_version=3.3.0
sodium_version=0.5.8
indium_version=1.0.30

View File

@@ -1,20 +1,20 @@
package fr.adrien1106.reframed;
import fr.adrien1106.reframed.util.ReFramedInteractionUtil;
import fr.adrien1106.reframed.block.*;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.itemgroup.v1.FabricItemGroup;
import net.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityTypeBuilder;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockSetType;
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.registry.Registries;
import net.minecraft.registry.Registry;
import net.minecraft.sound.BlockSoundGroup;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
@@ -24,49 +24,61 @@ import java.util.ArrayList;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import static fr.adrien1106.reframed.util.blocks.BlockProperties.LIGHT;
/**
* TODO handle grass side, multiple camos
* TODO make block pairable by right click -> for v1.6
* TODO add Hammer from framed ( removes theme ) -> for v1.5.5
* TODO add screwdriver ( iterate over theme states ) ?
* TODO add blueprint for survival friendly copy paste of a theme. -> for v1.5.5
* TODO add minecraft models like wall fence etc -> for v1.6
* TODO better connected textures -> maybe v1.6 ?
*/
public class ReFramed implements ModInitializer {
public static final String MODID = "reframed";
public static final ArrayList<Block> BLOCKS = new ArrayList<>();
public static Block CUBE, STAIRS, SLAB, POST, FENCE, FENCE_GATE, DOOR, TRAPDOOR, IRON_DOOR, IRON_TRAPDOOR, PRESSURE_PLATE, BUTTON, LEVER, WALL, CARPET, PANE, CANDLE;
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 ItemGroup ITEM_GROUP;
public static BlockEntityType<ReFramedEntity> REFRAMED_BLOCK_ENTITY;
public static BlockEntityType<ReFramedDoubleEntity> REFRAMED_DOUBLE_BLOCK_ENTITY;
public static BiConsumer<World, BlockPos> chunkRerenderProxy = (world, pos) -> {};
@Override
public void onInitialize() {
//registerReFramed mutates FRAMES as a side effect, which is a List, so order is preserved
//the ordering is used in the creative tab, so they're roughly sorted by encounter order of the
//corresponding vanilla block in the "search" creative tab... with the non-vanilla "post" and
//"vertical slab" inserted where they fit ...and i moved the lever way up next to the pressureplate
//and button, because they're redstoney... hopefully this ordering makes sense lol
CUBE = registerReFramed("cube" , new ReFramedBlock(ReFramedInteractionUtil.makeSettings()));
STAIRS = registerReFramed("stairs" , new ReFramedStairsBlock(cp(Blocks.OAK_STAIRS)));
SLAB = registerReFramed("slab" , new ReFramedSlabBlock(cp(Blocks.OAK_SLAB)));
POST = registerReFramed("post" , new ReFramedPostBlock(cp(Blocks.OAK_FENCE)));
FENCE = registerReFramed("fence" , new ReFramedFenceBlock(cp(Blocks.OAK_FENCE)));
FENCE_GATE = registerReFramed("fence_gate" , new ReFramedFenceGateBlock(cp(Blocks.OAK_FENCE_GATE)));
DOOR = registerReFramed("door" , new ReFramedDoorBlock(cp(Blocks.OAK_DOOR), BlockSetType.OAK));
TRAPDOOR = registerReFramed("trapdoor" , new ReFramedTrapdoorBlock(cp(Blocks.OAK_TRAPDOOR), BlockSetType.OAK));
IRON_DOOR = registerReFramed("iron_door" , new ReFramedDoorBlock(cp(Blocks.IRON_DOOR), BlockSetType.IRON));
IRON_TRAPDOOR = registerReFramed("iron_trapdoor" , new ReFramedTrapdoorBlock(cp(Blocks.IRON_TRAPDOOR), BlockSetType.IRON));
PRESSURE_PLATE = registerReFramed("pressure_plate", new ReFramedPressurePlateBlock(cp(Blocks.OAK_PRESSURE_PLATE)));
BUTTON = registerReFramed("button" , new ReFramedButtonBlock(cp(Blocks.OAK_BUTTON)));
LEVER = registerReFramed("lever" , new ReFramedLeverBlock(cp(Blocks.LEVER)));
WALL = registerReFramed("wall" , new ReFramedWallBlock(ReFramedInteractionUtil.makeSettings()));
CARPET = registerReFramed("carpet" , new ReFramedCarpetBlock(cp(Blocks.WHITE_CARPET)));
PANE = registerReFramed("pane" , new ReFramedPaneBlock(cp(Blocks.GLASS_PANE)));
CANDLE = registerReFramed("candle" , new ReFramedCandleBlock(ReFramedCandleBlock.configureSettings(cp(Blocks.CANDLE))));
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)));
REFRAMED_BLOCK_ENTITY = Registry.register(Registries.BLOCK_ENTITY_TYPE, id("camo"),
FabricBlockEntityTypeBuilder.create((pos, state) -> new ReFramedEntity(REFRAMED_BLOCK_ENTITY, pos, state), BLOCKS.toArray(new Block[0])).build(null)
FabricBlockEntityTypeBuilder.create(
(pos, state) -> new ReFramedEntity(REFRAMED_BLOCK_ENTITY, pos, state),
BLOCKS.stream()
.filter(block -> !(block instanceof ReFramedDoubleBlock))
.toArray(Block[]::new)).build(null)
);
Registry.register(Registries.ITEM_GROUP, id("tab"), FabricItemGroup.builder()
REFRAMED_DOUBLE_BLOCK_ENTITY = Registry.register(Registries.BLOCK_ENTITY_TYPE, id("double_camo"),
FabricBlockEntityTypeBuilder.create(
(pos, state) -> new ReFramedDoubleEntity(REFRAMED_DOUBLE_BLOCK_ENTITY, pos, state),
BLOCKS.stream()
.filter(block -> block instanceof ReFramedDoubleBlock)
.toArray(Block[]::new)).build(null)
);
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()
@@ -74,7 +86,13 @@ public class ReFramed implements ModInitializer {
}
private static AbstractBlock.Settings cp(Block base) {
return ReFramedInteractionUtil.configureSettings(AbstractBlock.Settings.copy(base));
return AbstractBlock.Settings.copy(base)
.luminance(state -> state.contains(LIGHT) && state.get(LIGHT) ? 15 : 0)
.nonOpaque()
.sounds(BlockSoundGroup.WOOD)
.hardness(0.2f)
.suffocates((a,b,c) -> false)
.blockVision((a,b,c) -> false);
}
private static <B extends Block> B registerReFramed(String path, B block) {

View File

@@ -1,29 +1,62 @@
package fr.adrien1106.reframed.block;
import com.google.common.base.MoreObjects;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.util.ReFramedInteractionUtil;
import fr.adrien1106.reframed.generator.RecipeSetter;
import fr.adrien1106.reframed.util.blocks.BlockHelper;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider;
import net.minecraft.block.*;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.data.server.recipe.RecipeExporter;
import net.minecraft.data.server.recipe.ShapedRecipeJsonBuilder;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.BlockItem;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.recipe.book.RecipeCategory;
import net.minecraft.state.StateManager;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.ItemScatterer;
import net.minecraft.util.collection.DefaultedList;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes;
import net.minecraft.world.BlockView;
import net.minecraft.world.GameRules;
import net.minecraft.world.World;
import org.jetbrains.annotations.Nullable;
public class ReFramedBlock extends Block implements BlockEntityProvider {
import java.util.List;
import static fr.adrien1106.reframed.util.blocks.BlockProperties.LIGHT;
public class ReFramedBlock extends Block implements BlockEntityProvider, RecipeSetter {
public ReFramedBlock(Settings settings) {
super(settings);
setDefaultState(ReFramedInteractionUtil.setDefaultStates(getDefaultState()));
setDefaultState(getDefaultState().with(LIGHT, false));
}
/**
* Generates a record for the key so that it replaces the blockstate
* which may have states that returns same models
* @param state - the state_key to generate the key from
* @return a cache key with only relevant properties
*/
public Object getModelCacheKey(BlockState state) {
return "";
}
/**
* @return the amount of models the block can have prevents allocating too much space for a model
*/
public int getModelStateCount() {
return 1;
}
//For addon devs: override this so your blocks don't end up trying to place my block entity, my BlockEntityType only handles blocks internal to the mod
@@ -35,51 +68,118 @@ public class ReFramedBlock extends Block implements BlockEntityProvider {
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(ReFramedInteractionUtil.appendProperties(builder));
super.appendProperties(builder.add(LIGHT));
}
@Nullable
@Override
public BlockState getPlacementState(ItemPlacementContext ctx) {
return ReFramedInteractionUtil.modifyPlacementState(super.getPlacementState(ctx), ctx);
return ReFramedEntity.getNbtLightLevel(super.getPlacementState(ctx), ctx.getStack());
}
@Override
public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
ActionResult r = ReFramedInteractionUtil.onUse(state, world, pos, player, hand, hit);
if(!r.isAccepted()) r = super.onUse(state, world, pos, player, hand, hit);
return r;
if (!canUse(world, pos, player)) return superUse(state, world, pos, player, hand, hit);
ActionResult result = BlockHelper.useUpgrade(state, world, pos, player, hand);
if (result.isAccepted()) return result;
return BlockHelper.useCamo(state, world, pos, player, hand, hit, 1);
}
// don't like this but might be useful
protected ActionResult superUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
return super.onUse(state, world, pos, player, hand, hit);
}
protected boolean canUse(World world, BlockPos pos, PlayerEntity player) {
return player.canModifyBlocks() && world.canPlayerModifyAt(player, pos);
}
@Override
public void onStateReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) {
ReFramedInteractionUtil.onStateReplaced(state, world, pos, newState, moved);
if(!state.isOf(newState.getBlock()) &&
world.getBlockEntity(pos) instanceof ReFramedEntity frame_entity &&
world.getGameRules().getBoolean(GameRules.DO_TILE_DROPS)
) {
DefaultedList<ItemStack> drops = DefaultedList.of();
List<BlockState> themes = frame_entity.getThemes();
themes.forEach(theme -> {
if(theme.getBlock() != Blocks.AIR) drops.add(new ItemStack(theme.getBlock()));
});
if(frame_entity.emitsRedstone()
&& themes.stream().noneMatch(theme -> theme.getWeakRedstonePower(world, pos, Direction.NORTH) != 0))
drops.add(new ItemStack(Items.REDSTONE_TORCH));
if(frame_entity.emitsLight()
&& themes.stream().noneMatch(theme -> theme.getLuminance() != 0))
drops.add(new ItemStack(Items.GLOWSTONE_DUST));
if(!frame_entity.isSolid()
&& themes.stream().anyMatch(AbstractBlockState::isSolid))
drops.add(new ItemStack(Items.POPPED_CHORUS_FRUIT));
ItemScatterer.spawn(world, pos, drops);
}
super.onStateReplaced(state, world, pos, newState, moved);
}
@Override
public void onPlaced(World world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) {
ReFramedInteractionUtil.onPlaced(world, pos, state, placer, stack);
if(world.isClient && world.getBlockEntity(pos) instanceof ReFramedEntity be) {
NbtCompound tag = BlockItem.getBlockEntityNbt(stack);
if(tag != null) be.readNbt(tag);
}
super.onPlaced(world, pos, state, placer, stack);
}
@Override
public VoxelShape getCollisionShape(BlockState state, BlockView view, BlockPos pos, ShapeContext ctx) {
return MoreObjects.firstNonNull(ReFramedInteractionUtil.getCollisionShape(state, view, pos, ctx), super.getCollisionShape(state, view, pos, ctx));
return isGhost(view, pos)
? VoxelShapes.empty()
: super.getCollisionShape(state, view, pos, ctx);
}
@Override
public boolean emitsRedstonePower(BlockState state) {
return ReFramedInteractionUtil.emitsRedstonePower(state);
public VoxelShape getCullingShape(BlockState state, BlockView view, BlockPos pos) {
return isGhost(view, pos)
? VoxelShapes.empty()
: super.getCullingShape(state, view, pos);
}
public VoxelShape getShape(BlockState state, int i) {
// assuming the shape don't need the world and position
return getOutlineShape(state, null, null, null);
}
public boolean isGhost(BlockView view, BlockPos pos) {
return view.getBlockEntity(pos) instanceof ReFramedEntity be && !be.isSolid();
}
@Override
public int getWeakRedstonePower(BlockState state, BlockView view, BlockPos pos, Direction dir) {
return ReFramedInteractionUtil.getWeakRedstonePower(state, view, pos, dir);
return view.getBlockEntity(pos) instanceof ReFramedEntity be && be.emitsRedstone() ? 15 : 0;
}
@Override
public int getStrongRedstonePower(BlockState state, BlockView view, BlockPos pos, Direction dir) {
return ReFramedInteractionUtil.getStrongRedstonePower(state, view, pos, dir);
return getWeakRedstonePower(state, view, pos, dir);
}
public int getTopThemeIndex(BlockState state) {
return 1;
}
@Override
public void setRecipe(RecipeExporter exporter) {
ShapedRecipeJsonBuilder
.create(RecipeCategory.BUILDING_BLOCKS, this)
.pattern("III")
.pattern("I~I")
.pattern("III")
.input('I', Items.BAMBOO)
.input('~', Items.STRING)
.criterion(FabricRecipeProvider.hasItem(Items.BAMBOO), FabricRecipeProvider.conditionsFromItem(Items.BAMBOO))
.criterion(FabricRecipeProvider.hasItem(this), FabricRecipeProvider.conditionsFromItem(this))
.offerTo(exporter);
}
}

View File

@@ -1,95 +0,0 @@
package fr.adrien1106.reframed.block;
import com.google.common.base.MoreObjects;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.util.ReFramedInteractionUtil;
import net.minecraft.block.Block;
import net.minecraft.block.BlockEntityProvider;
import net.minecraft.block.BlockSetType;
import net.minecraft.block.BlockState;
import net.minecraft.block.ButtonBlock;
import net.minecraft.block.ShapeContext;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.item.ItemStack;
import net.minecraft.state.StateManager;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
import org.jetbrains.annotations.Nullable;
public class ReFramedButtonBlock extends ButtonBlock implements BlockEntityProvider {
public ReFramedButtonBlock(Settings settings) {
this(settings, BlockSetType.OAK, 30);
}
public ReFramedButtonBlock(Settings settings, BlockSetType blockSetType, int i) {
super(blockSetType, i, settings);
setDefaultState(ReFramedInteractionUtil.setDefaultStates(getDefaultState()));
}
@Nullable
@Override
public BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
return ReFramed.REFRAMED_BLOCK_ENTITY.instantiate(pos, state);
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(ReFramedInteractionUtil.appendProperties(builder));
}
@Nullable
@Override
public BlockState getPlacementState(ItemPlacementContext ctx) {
return ReFramedInteractionUtil.modifyPlacementState(super.getPlacementState(ctx), ctx);
}
@Override
public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
ActionResult r = ReFramedInteractionUtil.onUse(state, world, pos, player, hand, hit);
if(!r.isAccepted()) r = super.onUse(state, world, pos, player, hand, hit);
return r;
}
@Override
public void onStateReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) {
ReFramedInteractionUtil.onStateReplaced(state, world, pos, newState, moved);
super.onStateReplaced(state, world, pos, newState, moved);
}
@Override
public void onPlaced(World world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) {
ReFramedInteractionUtil.onPlaced(world, pos, state, placer, stack);
}
@Override
public VoxelShape getCollisionShape(BlockState state, BlockView view, BlockPos pos, ShapeContext ctx) {
return MoreObjects.firstNonNull(ReFramedInteractionUtil.getCollisionShape(state, view, pos, ctx), super.getCollisionShape(state, view, pos, ctx));
}
@Override
public boolean emitsRedstonePower(BlockState state) {
return super.emitsRedstonePower(state);
}
@Override
public int getWeakRedstonePower(BlockState state, BlockView view, BlockPos pos, Direction dir) {
boolean a = 0 != super.getWeakRedstonePower(state, view, pos, dir);
boolean b = 0 != ReFramedInteractionUtil.getWeakRedstonePower(state, view, pos, dir);
return (a ^ b) ? 15 : 0;
}
@Override
public int getStrongRedstonePower(BlockState state, BlockView view, BlockPos pos, Direction dir) {
if(getDirection(state) != dir) return 0;
else return getWeakRedstonePower(state, view, pos, dir);
}
}

View File

@@ -1,92 +0,0 @@
package fr.adrien1106.reframed.block;
import com.google.common.base.MoreObjects;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.util.ReFramedInteractionUtil;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockEntityProvider;
import net.minecraft.block.BlockState;
import net.minecraft.block.CandleBlock;
import net.minecraft.block.ShapeContext;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.item.ItemStack;
import net.minecraft.state.StateManager;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
import org.jetbrains.annotations.Nullable;
public class ReFramedCandleBlock extends CandleBlock implements BlockEntityProvider {
public ReFramedCandleBlock(Settings settings) {
super(settings);
setDefaultState(ReFramedInteractionUtil.setDefaultStates(getDefaultState()));
}
public static AbstractBlock.Settings configureSettings(AbstractBlock.Settings in) {
return in.luminance(state -> Math.max(ReFramedInteractionUtil.luminance(state), CandleBlock.STATE_TO_LUMINANCE.applyAsInt(state)));
}
@Override
public @Nullable BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
return ReFramed.REFRAMED_BLOCK_ENTITY.instantiate(pos, state);
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(ReFramedInteractionUtil.appendProperties(builder));
}
@Nullable
@Override
public BlockState getPlacementState(ItemPlacementContext ctx) {
return ReFramedInteractionUtil.modifyPlacementState(super.getPlacementState(ctx), ctx);
}
@Override
public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
ActionResult r = ReFramedInteractionUtil.onUse(state, world, pos, player, hand, hit);
if(!r.isAccepted()) r = super.onUse(state, world, pos, player, hand, hit);
return r;
}
@Override
public void onStateReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) {
ReFramedInteractionUtil.onStateReplaced(state, world, pos, newState, moved);
super.onStateReplaced(state, world, pos, newState, moved);
}
@Override
public void onPlaced(World world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) {
ReFramedInteractionUtil.onPlaced(world, pos, state, placer, stack);
super.onPlaced(world, pos, state, placer, stack);
}
@Override
public VoxelShape getCollisionShape(BlockState state, BlockView view, BlockPos pos, ShapeContext ctx) {
return MoreObjects.firstNonNull(ReFramedInteractionUtil.getCollisionShape(state, view, pos, ctx), super.getCollisionShape(state, view, pos, ctx));
}
@Override
public boolean emitsRedstonePower(BlockState state) {
return ReFramedInteractionUtil.emitsRedstonePower(state);
}
@Override
public int getWeakRedstonePower(BlockState state, BlockView view, BlockPos pos, Direction dir) {
return ReFramedInteractionUtil.getWeakRedstonePower(state, view, pos, dir);
}
@Override
public int getStrongRedstonePower(BlockState state, BlockView view, BlockPos pos, Direction dir) {
return ReFramedInteractionUtil.getStrongRedstonePower(state, view, pos, dir);
}
}

View File

@@ -1,87 +0,0 @@
package fr.adrien1106.reframed.block;
import com.google.common.base.MoreObjects;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.util.ReFramedInteractionUtil;
import net.minecraft.block.Block;
import net.minecraft.block.BlockEntityProvider;
import net.minecraft.block.BlockState;
import net.minecraft.block.CarpetBlock;
import net.minecraft.block.ShapeContext;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.item.ItemStack;
import net.minecraft.state.StateManager;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
import org.jetbrains.annotations.Nullable;
public class ReFramedCarpetBlock extends CarpetBlock implements BlockEntityProvider {
public ReFramedCarpetBlock(Settings settings) {
super(settings);
setDefaultState(ReFramedInteractionUtil.setDefaultStates(getDefaultState()));
}
@Nullable
@Override
public BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
return ReFramed.REFRAMED_BLOCK_ENTITY.instantiate(pos, state);
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(ReFramedInteractionUtil.appendProperties(builder));
}
@Nullable
@Override
public BlockState getPlacementState(ItemPlacementContext ctx) {
return ReFramedInteractionUtil.modifyPlacementState(super.getPlacementState(ctx), ctx);
}
@Override
public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
ActionResult r = ReFramedInteractionUtil.onUse(state, world, pos, player, hand, hit);
if(!r.isAccepted()) r = super.onUse(state, world, pos, player, hand, hit);
return r;
}
@Override
public void onStateReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) {
ReFramedInteractionUtil.onStateReplaced(state, world, pos, newState, moved);
super.onStateReplaced(state, world, pos, newState, moved);
}
@Override
public void onPlaced(World world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) {
ReFramedInteractionUtil.onPlaced(world, pos, state, placer, stack);
}
@Override
public VoxelShape getCollisionShape(BlockState state, BlockView view, BlockPos pos, ShapeContext ctx) {
return MoreObjects.firstNonNull(ReFramedInteractionUtil.getCollisionShape(state, view, pos, ctx), super.getCollisionShape(state, view, pos, ctx));
}
@Override
public boolean emitsRedstonePower(BlockState state) {
return ReFramedInteractionUtil.emitsRedstonePower(state);
}
@Override
public int getWeakRedstonePower(BlockState state, BlockView view, BlockPos pos, Direction dir) {
return ReFramedInteractionUtil.getWeakRedstonePower(state, view, pos, dir);
}
@Override
public int getStrongRedstonePower(BlockState state, BlockView view, BlockPos pos, Direction dir) {
return ReFramedInteractionUtil.getStrongRedstonePower(state, view, pos, dir);
}
}

View File

@@ -1,89 +0,0 @@
package fr.adrien1106.reframed.block;
import com.google.common.base.MoreObjects;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.util.ReFramedInteractionUtil;
import net.minecraft.block.Block;
import net.minecraft.block.BlockEntityProvider;
import net.minecraft.block.BlockSetType;
import net.minecraft.block.BlockState;
import net.minecraft.block.DoorBlock;
import net.minecraft.block.ShapeContext;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.item.ItemStack;
import net.minecraft.state.StateManager;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
import org.jetbrains.annotations.Nullable;
public class ReFramedDoorBlock extends DoorBlock implements BlockEntityProvider {
public ReFramedDoorBlock(Settings settings, BlockSetType blockSetType) {
super(blockSetType, settings);
setDefaultState(ReFramedInteractionUtil.setDefaultStates(getDefaultState()));
}
@Nullable
@Override
public BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
return ReFramed.REFRAMED_BLOCK_ENTITY.instantiate(pos, state);
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(ReFramedInteractionUtil.appendProperties(builder));
}
@Nullable
@Override
public BlockState getPlacementState(ItemPlacementContext ctx) {
return ReFramedInteractionUtil.modifyPlacementState(super.getPlacementState(ctx), ctx);
}
@Override
public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
ActionResult r = ReFramedInteractionUtil.onUse(state, world, pos, player, hand, hit);
if(!r.isAccepted()) r = super.onUse(state, world, pos, player, hand, hit);
return r;
}
@Override
public void onStateReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) {
ReFramedInteractionUtil.onStateReplaced(state, world, pos, newState, moved);
super.onStateReplaced(state, world, pos, newState, moved);
}
@Override
public void onPlaced(World world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) {
ReFramedInteractionUtil.onPlaced(world, pos, state, placer, stack);
super.onPlaced(world, pos, state, placer, stack);
}
@Override
public VoxelShape getCollisionShape(BlockState state, BlockView view, BlockPos pos, ShapeContext ctx) {
return MoreObjects.firstNonNull(ReFramedInteractionUtil.getCollisionShape(state, view, pos, ctx), super.getCollisionShape(state, view, pos, ctx));
}
@Override
public boolean emitsRedstonePower(BlockState state) {
return ReFramedInteractionUtil.emitsRedstonePower(state);
}
@Override
public int getWeakRedstonePower(BlockState state, BlockView view, BlockPos pos, Direction dir) {
return ReFramedInteractionUtil.getWeakRedstonePower(state, view, pos, dir);
}
@Override
public int getStrongRedstonePower(BlockState state, BlockView view, BlockPos pos, Direction dir) {
return ReFramedInteractionUtil.getStrongRedstonePower(state, view, pos, dir);
}
}

View File

@@ -0,0 +1,77 @@
package fr.adrien1106.reframed.block;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.util.blocks.BlockHelper;
import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
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.shape.VoxelShape;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
import org.jetbrains.annotations.Nullable;
import static net.minecraft.util.shape.VoxelShapes.empty;
import static net.minecraft.util.shape.VoxelShapes.fullCube;
public abstract class ReFramedDoubleBlock extends ReFramedBlock {
public ReFramedDoubleBlock(Settings settings) {
super(settings);
}
@Override
public @Nullable BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
return ReFramed.REFRAMED_DOUBLE_BLOCK_ENTITY.instantiate(pos, state);
}
protected int getHitShape(BlockState state, BlockHitResult hit) {
Direction side = hit.getSide();
VoxelShape first_shape = getShape(state, 1);
VoxelShape second_shape = getShape(state, 2);
// Determine if any of the two shape is covering the side entirely
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;
return 0;
}
@Override
public boolean isTransparent(BlockState state, BlockView world, BlockPos pos) {
return world.getBlockEntity(pos) instanceof ThemeableBlockEntity framed_entity
&& framed_entity.getThemes().stream().allMatch(theme -> theme.isTransparent(world, pos));
}
public VoxelShape getRenderOutline(BlockState state, BlockHitResult hit) {
return getShape(state, getHitShape(state, hit));
}
@Override
public VoxelShape getCollisionShape(BlockState state, BlockView view, BlockPos pos, ShapeContext ctx) {
return getCullingShape(state, view, pos);
}
@Override
public VoxelShape getCullingShape(BlockState state, BlockView view, BlockPos pos) {
return isGhost(view, pos) ? empty() : fullCube();
}
@Override
public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
if (!canUse(world, pos, player)) return superUse(state, world, pos, player, hand, hit);
ActionResult result = BlockHelper.useUpgrade(state, world, pos, player, hand);
if (result.isAccepted()) return result;
return BlockHelper.useCamo(state, world, pos, player, hand, hit, getHitShape(state, hit));
}
}

View File

@@ -0,0 +1,62 @@
package fr.adrien1106.reframed.block;
import fr.adrien1106.reframed.ReFramed;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.entity.BlockEntityType;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtHelper;
import net.minecraft.registry.Registries;
import net.minecraft.util.math.BlockPos;
import java.util.List;
import java.util.Objects;
public class ReFramedDoubleEntity extends ReFramedEntity {
protected BlockState second_state = Blocks.AIR.getDefaultState();
public ReFramedDoubleEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) {
super(type, pos, state);
}
@Override
public BlockState getTheme(int i) {
return i == 2 ? second_state : super.getTheme(i);
}
@Override
public List<BlockState> getThemes() {
List<BlockState> themes = super.getThemes();
themes.add(second_state);
return themes;
}
public void setTheme(BlockState new_state, int i) {
if(i == 2) {
if (Objects.equals(second_state, new_state)) return;
second_state = new_state;
markDirtyAndDispatch();
} else super.setTheme(new_state, i);
}
@Override
public void readNbt(NbtCompound nbt) {
super.readNbt(nbt);
BlockState rendered_state = second_state;// keep previous state_key to check if rerender is needed
second_state = NbtHelper.toBlockState(Registries.BLOCK.getReadOnlyWrapper(), nbt.getCompound(BLOCKSTATE_KEY + 2));
// Force a chunk remesh on the client if the displayed blockstate has changed
if(world != null && world.isClient && !Objects.equals(rendered_state, second_state)) {
ReFramed.chunkRerenderProxy.accept(world, pos);
}
}
@Override
public void writeNbt(NbtCompound nbt) {
super.writeNbt(nbt);
if(second_state != Blocks.AIR.getDefaultState()) nbt.put(BLOCKSTATE_KEY + 2, NbtHelper.fromBlockState(second_state));
}
}

View File

@@ -1,8 +1,8 @@
package fr.adrien1106.reframed.block;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.util.ReFramedInteractionUtil;
import fr.adrien1106.reframed.util.ThemeableBlockEntity;
import fr.adrien1106.reframed.util.blocks.BlockProperties;
import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.entity.BlockEntity;
@@ -21,26 +21,22 @@ import net.minecraft.util.math.BlockPos;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
//Keeping the weight of this block entity down, both in terms of memory consumption and NBT sync traffic,
//is pretty important since players might place a lot of them. There were tons and tons of these at Blanketcon.
//To that end, most of the state has been crammed into a bitfield.
//To that end, most of the state_key has been crammed into a bitfield.
public class ReFramedEntity extends BlockEntity implements ThemeableBlockEntity {
protected BlockState renderedState = Blocks.AIR.getDefaultState();
protected byte bitfield = DEFAULT_BITFIELD;
protected BlockState first_state = Blocks.AIR.getDefaultState();
protected byte bit_field = SOLIDITY_MASK;
protected static final int SPENT_GLOWSTONE_DUST_MASK = 0b00000001;
protected static final int SPENT_REDSTONE_TORCH_MASK = 0b00000010;
protected static final int SPENT_POPPED_CHORUS_MASK = 0b00000100;
protected static final int EMITS_REDSTONE_MASK = 0b00001000;
protected static final int IS_SOLID_MASK = 0b00010000;
protected static final byte DEFAULT_BITFIELD = IS_SOLID_MASK; //brand-new frames shall be solid
//Using one-character names is a little brash, like, what if there's a mod that adds crap to the NBT of every
//block entity, and uses short names for the same reason I am (because there are lots and lots of block entities)?
//Kinda doubt it?
protected static final String BLOCKSTATE_KEY = "s";
protected static final byte LIGHT_MASK = 0b001;
protected static final byte REDSTONE_MASK = 0b010;
protected static final byte SOLIDITY_MASK = 0b100;
public static final String BLOCKSTATE_KEY = "s";
protected static final String BITFIELD_KEY = "b";
public ReFramedEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) {
@@ -48,145 +44,116 @@ public class ReFramedEntity extends BlockEntity implements ThemeableBlockEntity
}
@Override
public void readNbt(NbtCompound tag) {
super.readNbt(tag);
public void readNbt(NbtCompound nbt) {
super.readNbt(nbt);
BlockState lastRenderedState = renderedState;
BlockState rendered_state = first_state; // keep previous state_key to check if rerender is needed
first_state = NbtHelper.toBlockState(Registries.BLOCK.getReadOnlyWrapper(), nbt.getCompound(BLOCKSTATE_KEY + 1));
if (nbt.contains(BITFIELD_KEY)) bit_field = nbt.getByte(BITFIELD_KEY);
if(tag.contains("BlockState")) { //2.0.4 and earlier
renderedState = NbtHelper.toBlockState(Registries.BLOCK.getReadOnlyWrapper(), tag.getCompound("BlockState"));
if(tag.getBoolean("spentglow")) spentGlowstoneDust();
if(tag.getBoolean("spentredst")) spentRedstoneTorch();
if(tag.getBoolean("spentchor")) spentPoppedChorus();
setEmitsRedstone(tag.getBoolean("emitsredst"));
setSolidity(!tag.contains("solid") || tag.getBoolean("solid")); //default to "true" if it's nonexistent
} else {
renderedState = NbtHelper.toBlockState(Registries.BLOCK.getReadOnlyWrapper(), tag.getCompound(BLOCKSTATE_KEY));
bitfield = tag.contains(BITFIELD_KEY) ? tag.getByte(BITFIELD_KEY) : DEFAULT_BITFIELD;
}
//Force a chunk remesh on the client if the displayed blockstate has changed
if(world != null && world.isClient && !Objects.equals(lastRenderedState, renderedState)) {
// Force a chunk remesh on the client if the displayed blockstate has changed
if(world != null && world.isClient && !Objects.equals(rendered_state, first_state)) {
ReFramed.chunkRerenderProxy.accept(world, pos);
}
}
@Override
public void writeNbt(NbtCompound tag) {
super.writeNbt(tag);
public void writeNbt(NbtCompound nbt) {
super.writeNbt(nbt);
if(renderedState != Blocks.AIR.getDefaultState()) tag.put(BLOCKSTATE_KEY, NbtHelper.fromBlockState(renderedState));
if(bitfield != DEFAULT_BITFIELD) tag.putByte(BITFIELD_KEY, bitfield);
if(first_state != Blocks.AIR.getDefaultState()) nbt.put(BLOCKSTATE_KEY + 1, NbtHelper.fromBlockState(first_state));
if(bit_field != SOLIDITY_MASK) nbt.putByte(BITFIELD_KEY, bit_field);
}
public static @NotNull BlockState readStateFromItem(ItemStack stack) {
NbtCompound blockEntityTag = BlockItem.getBlockEntityNbt(stack);
if(blockEntityTag == null) return Blocks.AIR.getDefaultState();
public static @NotNull BlockState readStateFromItem(ItemStack stack, int state) {
NbtCompound nbt = BlockItem.getBlockEntityNbt(stack);
if(nbt == null) return Blocks.AIR.getDefaultState();
//slightly paranoid NBT handling cause you never know what mysteries are afoot with items
NbtElement subElement;
if(blockEntityTag.contains(BLOCKSTATE_KEY)) subElement = blockEntityTag.get(BLOCKSTATE_KEY); //2.0.5
else if(blockEntityTag.contains("BlockState")) subElement = blockEntityTag.get("BlockState"); //old 2.0.4 items
NbtElement element;
if(nbt.contains(BLOCKSTATE_KEY + state)) element = nbt.get(BLOCKSTATE_KEY + state);
else return Blocks.AIR.getDefaultState();
if(!(subElement instanceof NbtCompound subCompound)) return Blocks.AIR.getDefaultState();
else return NbtHelper.toBlockState(Registries.BLOCK.getReadOnlyWrapper(), subCompound);
if(!(element instanceof NbtCompound compound)) return Blocks.AIR.getDefaultState();
else return NbtHelper.toBlockState(Registries.BLOCK.getReadOnlyWrapper(), compound);
}
//Awkward: usually the BlockState is the source of truth for things like the "emits light" blockstate, but if you
//ctrl-pick a glowing block and place it, it should still be glowing. This is some hacky shit that guesses the value of
//the LIGHT blockstate based off information in the NBT tag, and also prevents bugginess like "the blockstate is not
//glowing but the copied NBT thinks glowstone dust was already added, so it refuses to accept more dust"
public static @Nullable BlockState weirdNbtLightLevelStuff(@Nullable BlockState state, ItemStack stack) {
public static @Nullable BlockState getNbtLightLevel(@Nullable BlockState state, ItemStack stack) {
if(state == null || stack == null) return state;
NbtCompound blockEntityTag = BlockItem.getBlockEntityNbt(stack);
if(blockEntityTag == null) return state;
NbtCompound nbt = BlockItem.getBlockEntityNbt(stack);
if(nbt == null) return state;
if(state.contains(ReFramedInteractionUtil.LIGHT)) {
state = state.with(ReFramedInteractionUtil.LIGHT,
blockEntityTag.getBoolean("spentglow") || //2.0.4
((blockEntityTag.contains(BITFIELD_KEY) ? blockEntityTag.getByte(BITFIELD_KEY) : DEFAULT_BITFIELD) & SPENT_GLOWSTONE_DUST_MASK) != 0 || //2.0.5
readStateFromItem(stack).getLuminance() != 0 //glowstone dust wasn't manually added, the block just emits light
if(state.contains(BlockProperties.LIGHT)) {
state = state.with(BlockProperties.LIGHT,
((nbt.contains(BITFIELD_KEY)
? nbt.getByte(BITFIELD_KEY)
: SOLIDITY_MASK)
& LIGHT_MASK) != 0
);
}
return state;
}
//RenderAttachmentBlockEntity impl. Note that ThemeableBlockEntity depends on this returning a BlockState object.
@Override
public BlockState getRenderAttachmentData() {
return renderedState;
public BlockState getTheme(int i) {
return first_state;
}
public void setRenderedState(BlockState newState) {
if(!Objects.equals(renderedState, newState)) {
renderedState = newState;
@Override
public List<BlockState> getThemes() {
List<BlockState> themes = new ArrayList<>();
themes.add(first_state);
return themes;
}
public void setTheme(BlockState new_state, int i) {
if(!Objects.equals(first_state, new_state)) {
first_state = new_state;
markDirtyAndDispatch();
}
}
public boolean hasSpentGlowstoneDust() {
return (bitfield & SPENT_GLOWSTONE_DUST_MASK) != 0;
/* --------------------------------------------------- ADDONS --------------------------------------------------- */
public boolean emitsLight() {
return (bit_field & LIGHT_MASK) != 0;
}
public void spentGlowstoneDust() {
bitfield |= SPENT_GLOWSTONE_DUST_MASK;
public void toggleLight() {
if (emitsLight()) bit_field &= ~LIGHT_MASK;
else bit_field |= LIGHT_MASK;
markDirtyAndDispatch();
}
public boolean hasSpentRedstoneTorch() {
return (bitfield & SPENT_REDSTONE_TORCH_MASK) != 0;
}
public void spentRedstoneTorch() {
bitfield |= SPENT_REDSTONE_TORCH_MASK;
public void toggleRedstone() {
if (emitsRedstone()) bit_field &= ~REDSTONE_MASK;
else bit_field |= REDSTONE_MASK;
if(world != null) world.updateNeighbors(pos, getCachedState().getBlock());
markDirtyAndDispatch();
}
public boolean hasSpentPoppedChorus() {
return (bitfield & SPENT_POPPED_CHORUS_MASK) != 0;
}
public void spentPoppedChorus() {
bitfield |= SPENT_POPPED_CHORUS_MASK;
markDirtyAndDispatch();
}
public boolean emitsRedstone() {
return (bitfield & EMITS_REDSTONE_MASK) != 0;
return (bit_field & REDSTONE_MASK) != 0;
}
public void setEmitsRedstone(boolean nextEmitsRedstone) {
boolean currentlyEmitsRedstone = emitsRedstone();
if(currentlyEmitsRedstone != nextEmitsRedstone) {
if(currentlyEmitsRedstone) bitfield &= ~EMITS_REDSTONE_MASK;
else bitfield |= EMITS_REDSTONE_MASK;
markDirtyAndDispatch();
if(world != null) world.updateNeighbors(pos, getCachedState().getBlock());
}
public void toggleSolidity() {
if (isSolid()) bit_field &= ~SOLIDITY_MASK;
else bit_field |= SOLIDITY_MASK;
if(world != null) world.setBlockState(pos, getCachedState());
markDirtyAndDispatch();
}
public boolean isSolid() {
return (bitfield & IS_SOLID_MASK) != 0;
return (bit_field & SOLIDITY_MASK) != 0;
}
public void setSolidity(boolean nextSolid) {
boolean currentlySolid = isSolid();
if(currentlySolid != nextSolid) {
if(currentlySolid) bitfield &= ~IS_SOLID_MASK;
else bitfield |= IS_SOLID_MASK;
markDirtyAndDispatch();
if(world != null) world.setBlockState(pos, getCachedState()); //do i need to invalidate any shape caches or something
}
}
//<standard blockentity boilerplate>
@Nullable
@Override
public Packet<ClientPlayPacketListener> toUpdatePacket() {
@@ -195,8 +162,6 @@ public class ReFramedEntity extends BlockEntity implements ThemeableBlockEntity
@Override
public NbtCompound toInitialChunkDataNbt() {
//TERRIBLE yarn name, this is "getUpdateTag", it's the nbt that will be sent to clients
//and it just calls "writeNbt"
return createNbt();
}
@@ -208,5 +173,4 @@ public class ReFramedEntity extends BlockEntity implements ThemeableBlockEntity
markDirty();
dispatch();
}
//</standard blockentity boilerplate>
}

View File

@@ -1,87 +0,0 @@
package fr.adrien1106.reframed.block;
import com.google.common.base.MoreObjects;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.util.ReFramedInteractionUtil;
import net.minecraft.block.Block;
import net.minecraft.block.BlockEntityProvider;
import net.minecraft.block.BlockState;
import net.minecraft.block.FenceBlock;
import net.minecraft.block.ShapeContext;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.item.ItemStack;
import net.minecraft.state.StateManager;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
import org.jetbrains.annotations.Nullable;
public class ReFramedFenceBlock extends FenceBlock implements BlockEntityProvider {
public ReFramedFenceBlock(Settings settings) {
super(settings);
setDefaultState(ReFramedInteractionUtil.setDefaultStates(getDefaultState()));
}
@Nullable
@Override
public BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
return ReFramed.REFRAMED_BLOCK_ENTITY.instantiate(pos, state);
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(ReFramedInteractionUtil.appendProperties(builder));
}
@Nullable
@Override
public BlockState getPlacementState(ItemPlacementContext ctx) {
return ReFramedInteractionUtil.modifyPlacementState(super.getPlacementState(ctx), ctx);
}
@Override
public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
ActionResult r = ReFramedInteractionUtil.onUse(state, world, pos, player, hand, hit);
if(!r.isAccepted()) r = super.onUse(state, world, pos, player, hand, hit);
return r;
}
@Override
public void onStateReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) {
ReFramedInteractionUtil.onStateReplaced(state, world, pos, newState, moved);
super.onStateReplaced(state, world, pos, newState, moved);
}
@Override
public void onPlaced(World world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) {
ReFramedInteractionUtil.onPlaced(world, pos, state, placer, stack);
}
@Override
public VoxelShape getCollisionShape(BlockState state, BlockView view, BlockPos pos, ShapeContext ctx) {
return MoreObjects.firstNonNull(ReFramedInteractionUtil.getCollisionShape(state, view, pos, ctx), super.getCollisionShape(state, view, pos, ctx));
}
@Override
public boolean emitsRedstonePower(BlockState state) {
return ReFramedInteractionUtil.emitsRedstonePower(state);
}
@Override
public int getWeakRedstonePower(BlockState state, BlockView view, BlockPos pos, Direction dir) {
return ReFramedInteractionUtil.getWeakRedstonePower(state, view, pos, dir);
}
@Override
public int getStrongRedstonePower(BlockState state, BlockView view, BlockPos pos, Direction dir) {
return ReFramedInteractionUtil.getStrongRedstonePower(state, view, pos, dir);
}
}

View File

@@ -1,98 +0,0 @@
package fr.adrien1106.reframed.block;
import com.google.common.base.MoreObjects;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.util.ReFramedInteractionUtil;
import fr.adrien1106.reframed.util.ReframedInteractible;
import net.minecraft.block.Block;
import net.minecraft.block.BlockEntityProvider;
import net.minecraft.block.BlockState;
import net.minecraft.block.FenceGateBlock;
import net.minecraft.block.ShapeContext;
import net.minecraft.block.WoodType;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.item.ItemStack;
import net.minecraft.state.StateManager;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
import org.jetbrains.annotations.Nullable;
public class ReFramedFenceGateBlock extends FenceGateBlock implements BlockEntityProvider, ReframedInteractible {
public ReFramedFenceGateBlock(Settings settings, WoodType woodType) {
super(woodType, settings);
setDefaultState(ReFramedInteractionUtil.setDefaultStates(getDefaultState()));
}
public ReFramedFenceGateBlock(Settings settings) {
this(settings, WoodType.OAK);
}
@Nullable
@Override
public BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
return ReFramed.REFRAMED_BLOCK_ENTITY.instantiate(pos, state);
}
@Nullable
@Override
public BlockState getPlacementState(ItemPlacementContext ctx) {
return ReFramedInteractionUtil.modifyPlacementState(super.getPlacementState(ctx), ctx);
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(ReFramedInteractionUtil.appendProperties(builder));
}
@Override
public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
ActionResult r = ReFramedInteractionUtil.onUse(state, world, pos, player, hand, hit);
if(!r.isAccepted()) r = super.onUse(state, world, pos, player, hand, hit);
return r;
}
@Override
public void onStateReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) {
ReFramedInteractionUtil.onStateReplaced(state, world, pos, newState, moved);
super.onStateReplaced(state, world, pos, newState, moved);
}
@Override
public void onPlaced(World world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) {
ReFramedInteractionUtil.onPlaced(world, pos, state, placer, stack);
}
@Override
public VoxelShape getCollisionShape(BlockState state, BlockView view, BlockPos pos, ShapeContext ctx) {
return MoreObjects.firstNonNull(ReFramedInteractionUtil.getCollisionShape(state, view, pos, ctx), super.getCollisionShape(state, view, pos, ctx));
}
@Override
public boolean emitsRedstonePower(BlockState state) {
return ReFramedInteractionUtil.emitsRedstonePower(state);
}
@Override
public int getWeakRedstonePower(BlockState state, BlockView view, BlockPos pos, Direction dir) {
return ReFramedInteractionUtil.getWeakRedstonePower(state, view, pos, dir);
}
@Override
public int getStrongRedstonePower(BlockState state, BlockView view, BlockPos pos, Direction dir) {
return ReFramedInteractionUtil.getStrongRedstonePower(state, view, pos, dir);
}
@Override
public boolean canAddRedstoneEmission(BlockState state, BlockView view, BlockPos pos) {
return false;
}
}

View File

@@ -0,0 +1,194 @@
package fr.adrien1106.reframed.block;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.generator.BlockStateProvider;
import fr.adrien1106.reframed.generator.GBlockstate;
import fr.adrien1106.reframed.util.VoxelHelper;
import fr.adrien1106.reframed.util.blocks.BlockHelper;
import fr.adrien1106.reframed.util.blocks.Corner;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext;
import net.minecraft.data.client.BlockStateSupplier;
import net.minecraft.data.client.MultipartBlockStateSupplier;
import net.minecraft.data.server.recipe.RecipeExporter;
import net.minecraft.data.server.recipe.RecipeProvider;
import net.minecraft.data.server.recipe.ShapedRecipeJsonBuilder;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.recipe.book.RecipeCategory;
import net.minecraft.state.StateManager;
import net.minecraft.util.Identifier;
import net.minecraft.util.function.BooleanBiFunction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes;
import net.minecraft.world.BlockView;
import org.jetbrains.annotations.Nullable;
import static fr.adrien1106.reframed.util.VoxelHelper.VoxelListBuilder;
import static fr.adrien1106.reframed.util.blocks.BlockProperties.CORNER;
import static fr.adrien1106.reframed.util.blocks.BlockProperties.CORNER_FACE;
import static fr.adrien1106.reframed.util.blocks.Corner.*;
import static net.minecraft.data.client.VariantSettings.Rotation.*;
public class ReFramedHalfStairBlock extends WaterloggableReFramedBlock implements BlockStateProvider {
public static final VoxelShape[] HALF_STAIR_VOXELS;
private record ModelCacheKey(Corner corner, int face) {}
public ReFramedHalfStairBlock(Settings settings) {
super(settings);
setDefaultState(getDefaultState().with(CORNER, NORTH_EAST_DOWN).with(CORNER_FACE, 0));
}
@Override
public Object getModelCacheKey(BlockState state) {
return new ModelCacheKey(state.get(CORNER), state.get(CORNER_FACE));
}
@Override
public int getModelStateCount() {
return 24;
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(builder.add(CORNER,CORNER_FACE));
}
@Override
public @Nullable BlockState getPlacementState(ItemPlacementContext ctx) {
Corner corner = BlockHelper.getPlacementCorner(ctx);
return super.getPlacementState(ctx)
.with(CORNER, corner)
.with(CORNER_FACE, corner.getDirectionIndex(ctx.getSide().getOpposite()));
}
@Override
public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return HALF_STAIR_VOXELS[state.get(CORNER_FACE) + state.get(CORNER).getID() * 3];
}
@Override
public BlockStateSupplier getMultipart() {
return getHalfStairMultipart(
this,
ReFramed.id("half_stair_down_special"),
ReFramed.id("half_stair_side_special")
);
}
public static BlockStateSupplier getHalfStairMultipart(Block block, Identifier model_down, Identifier model_side) {
return MultipartBlockStateSupplier.create(block)
.with(GBlockstate.when(CORNER, NORTH_EAST_DOWN, CORNER_FACE, 1),
GBlockstate.variant(model_side, true, R0, R0))
.with(GBlockstate.when(CORNER, NORTH_EAST_DOWN, CORNER_FACE, 0),
GBlockstate.variant(model_side, true, R90, R270))
.with(GBlockstate.when(CORNER, NORTH_EAST_DOWN, CORNER_FACE, 2),
GBlockstate.variant(model_down, true, R0, R0))
.with(GBlockstate.when(CORNER, EAST_SOUTH_DOWN, CORNER_FACE, 1),
GBlockstate.variant(model_side, true, R0, R90))
.with(GBlockstate.when(CORNER, EAST_SOUTH_DOWN, CORNER_FACE, 0),
GBlockstate.variant(model_side, true, R90, R0))
.with(GBlockstate.when(CORNER, EAST_SOUTH_DOWN, CORNER_FACE, 2),
GBlockstate.variant(model_down, true, R0, R90))
.with(GBlockstate.when(CORNER, SOUTH_WEST_DOWN, CORNER_FACE, 1),
GBlockstate.variant(model_side, true, R0, R180))
.with(GBlockstate.when(CORNER, SOUTH_WEST_DOWN, CORNER_FACE, 0),
GBlockstate.variant(model_side, true, R90, R90))
.with(GBlockstate.when(CORNER, SOUTH_WEST_DOWN, CORNER_FACE, 2),
GBlockstate.variant(model_down, true, R0, R180))
.with(GBlockstate.when(CORNER, WEST_NORTH_DOWN, CORNER_FACE, 1),
GBlockstate.variant(model_side, true, R0, R270))
.with(GBlockstate.when(CORNER, WEST_NORTH_DOWN, CORNER_FACE, 0),
GBlockstate.variant(model_side, true, R90, R180))
.with(GBlockstate.when(CORNER, WEST_NORTH_DOWN, CORNER_FACE, 2),
GBlockstate.variant(model_down, true, R0, R270))
.with(GBlockstate.when(CORNER, EAST_SOUTH_UP, CORNER_FACE, 0),
GBlockstate.variant(model_side, true, R180, R0))
.with(GBlockstate.when(CORNER, EAST_SOUTH_UP, CORNER_FACE, 1),
GBlockstate.variant(model_side, true, R270, R90))
.with(GBlockstate.when(CORNER, EAST_SOUTH_UP, CORNER_FACE, 2),
GBlockstate.variant(model_down, true, R180, R0))
.with(GBlockstate.when(CORNER, SOUTH_WEST_UP, CORNER_FACE, 0),
GBlockstate.variant(model_side, true, R180, R90))
.with(GBlockstate.when(CORNER, SOUTH_WEST_UP, CORNER_FACE, 1),
GBlockstate.variant(model_side, true, R270, R180))
.with(GBlockstate.when(CORNER, SOUTH_WEST_UP, CORNER_FACE, 2),
GBlockstate.variant(model_down, true, R180, R90))
.with(GBlockstate.when(CORNER, WEST_NORTH_UP, CORNER_FACE, 0),
GBlockstate.variant(model_side, true, R180, R180))
.with(GBlockstate.when(CORNER, WEST_NORTH_UP, CORNER_FACE, 1),
GBlockstate.variant(model_side, true, R270, R270))
.with(GBlockstate.when(CORNER, WEST_NORTH_UP, CORNER_FACE, 2),
GBlockstate.variant(model_down, true, R180, R180))
.with(GBlockstate.when(CORNER, NORTH_EAST_UP, CORNER_FACE, 0),
GBlockstate.variant(model_side, true, R180, R270))
.with(GBlockstate.when(CORNER, NORTH_EAST_UP, CORNER_FACE, 1),
GBlockstate.variant(model_side, true, R270, R0))
.with(GBlockstate.when(CORNER, NORTH_EAST_UP, CORNER_FACE, 2),
GBlockstate.variant(model_down, true, R180, R270));
}
@Override
public void setRecipe(RecipeExporter exporter) {
RecipeProvider.offerStonecuttingRecipe(exporter, RecipeCategory.BUILDING_BLOCKS, this, ReFramed.CUBE, 2);
ShapedRecipeJsonBuilder
.create(RecipeCategory.BUILDING_BLOCKS, this, 4)
.pattern("I ")
.pattern("II ")
.input('I', ReFramed.CUBE)
.criterion(FabricRecipeProvider.hasItem(ReFramed.CUBE), FabricRecipeProvider.conditionsFromItem(ReFramed.CUBE))
.criterion(FabricRecipeProvider.hasItem(this), FabricRecipeProvider.conditionsFromItem(this))
.offerTo(exporter);
}
static {
final VoxelShape HALF_STAIR = VoxelShapes.combineAndSimplify(
createCuboidShape(8, 0, 0, 16, 16, 8),
createCuboidShape(0, 0, 0, 8, 8, 8),
BooleanBiFunction.OR
);
HALF_STAIR_VOXELS = VoxelListBuilder.create(HALF_STAIR, 24)
.add(0 , VoxelHelper::rotateY, VoxelHelper::mirrorZ)
.add(0 , VoxelHelper::rotateCX, VoxelHelper::mirrorZ)
.add(0 , VoxelHelper::rotateY)
.add(1 , VoxelHelper::rotateY)
.add(2 , VoxelHelper::rotateY)
.add(3 , VoxelHelper::rotateY)
.add(4 , VoxelHelper::rotateY)
.add(5 , VoxelHelper::rotateY)
.add(6 , VoxelHelper::rotateY)
.add(7 , VoxelHelper::rotateY)
.add(8 , VoxelHelper::rotateY)
.add(0 , VoxelHelper::mirrorY)
.add(1 , VoxelHelper::mirrorY)
.add(2 , VoxelHelper::mirrorY)
.add(12, VoxelHelper::rotateY)
.add(13, VoxelHelper::rotateY)
.add(14, VoxelHelper::rotateY)
.add(15, VoxelHelper::rotateY)
.add(16, VoxelHelper::rotateY)
.add(17, VoxelHelper::rotateY)
.add(18, VoxelHelper::rotateY)
.add(19, VoxelHelper::rotateY)
.add(20, VoxelHelper::rotateY)
.build();
}
}

View File

@@ -0,0 +1,103 @@
package fr.adrien1106.reframed.block;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.generator.BlockStateProvider;
import fr.adrien1106.reframed.util.blocks.BlockHelper;
import fr.adrien1106.reframed.util.blocks.Corner;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext;
import net.minecraft.data.client.BlockStateSupplier;
import net.minecraft.data.server.recipe.RecipeExporter;
import net.minecraft.data.server.recipe.RecipeProvider;
import net.minecraft.data.server.recipe.ShapelessRecipeJsonBuilder;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.recipe.book.RecipeCategory;
import net.minecraft.state.StateManager;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.BlockView;
import org.jetbrains.annotations.Nullable;
import static fr.adrien1106.reframed.block.ReFramedHalfStairBlock.HALF_STAIR_VOXELS;
import static fr.adrien1106.reframed.block.ReFramedHalfStairBlock.getHalfStairMultipart;
import static fr.adrien1106.reframed.block.ReFramedSlabBlock.getSlabShape;
import static fr.adrien1106.reframed.block.ReFramedSmallCubeBlock.SMALL_CUBE_VOXELS;
import static fr.adrien1106.reframed.util.blocks.BlockProperties.CORNER;
import static fr.adrien1106.reframed.util.blocks.BlockProperties.CORNER_FACE;
import static fr.adrien1106.reframed.util.blocks.Corner.NORTH_EAST_DOWN;
import static net.minecraft.util.shape.VoxelShapes.empty;
public class ReFramedHalfStairsSlabBlock extends WaterloggableReFramedDoubleBlock implements BlockStateProvider {
private record ModelCacheKey(Corner corner, int face) {}
public ReFramedHalfStairsSlabBlock(Settings settings) {
super(settings);
setDefaultState(getDefaultState().with(CORNER, NORTH_EAST_DOWN).with(CORNER_FACE, 0));
}
@Override
public Object getModelCacheKey(BlockState state) {
return new ModelCacheKey(state.get(CORNER), state.get(CORNER_FACE));
}
@Override
public int getModelStateCount() {
return 24;
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(builder.add(CORNER,CORNER_FACE));
}
@Override
public @Nullable BlockState getPlacementState(ItemPlacementContext ctx) {
Corner corner = BlockHelper.getPlacementCorner(ctx);
return super.getPlacementState(ctx)
.with(CORNER, corner)
.with(CORNER_FACE, corner.getDirectionIndex(ctx.getSide().getOpposite()));
}
@Override
public VoxelShape getCullingShape(BlockState state, BlockView view, BlockPos pos) {
return isGhost(view, pos) ? empty(): getSlabShape(state.get(CORNER).getDirection(state.get(CORNER_FACE)));
}
@Override
public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return getSlabShape(state.get(CORNER).getDirection(state.get(CORNER_FACE)));
}
@Override
public VoxelShape getShape(BlockState state, int i) {
Corner corner = state.get(CORNER);
int face = state.get(CORNER_FACE);
return i == 2
? SMALL_CUBE_VOXELS[corner.getOpposite(face).getID()]
: HALF_STAIR_VOXELS[face + corner.getID() * 3];
}
@Override
public BlockStateSupplier getMultipart() {
return getHalfStairMultipart(
this,
ReFramed.id("half_stairs_slab_down_special"),
ReFramed.id("half_stairs_slab_side_special")
);
}
@Override
public void setRecipe(RecipeExporter exporter) {
RecipeProvider.offerStonecuttingRecipe(exporter, RecipeCategory.BUILDING_BLOCKS, this, ReFramed.CUBE, 2);
ShapelessRecipeJsonBuilder
.create(RecipeCategory.BUILDING_BLOCKS, this)
.input(ReFramed.HALF_STAIR)
.input(ReFramed.SMALL_CUBE)
.criterion(FabricRecipeProvider.hasItem(ReFramed.CUBE), FabricRecipeProvider.conditionsFromItem(ReFramed.CUBE))
.criterion(FabricRecipeProvider.hasItem(this), FabricRecipeProvider.conditionsFromItem(this))
.offerTo(exporter);
}
}

View File

@@ -0,0 +1,138 @@
package fr.adrien1106.reframed.block;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.generator.BlockStateProvider;
import fr.adrien1106.reframed.generator.GBlockstate;
import fr.adrien1106.reframed.util.blocks.BlockHelper;
import fr.adrien1106.reframed.util.blocks.Corner;
import fr.adrien1106.reframed.util.blocks.Edge;
import fr.adrien1106.reframed.util.blocks.StairShape;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext;
import net.minecraft.data.client.BlockStateSupplier;
import net.minecraft.data.client.MultipartBlockStateSupplier;
import net.minecraft.data.server.recipe.RecipeExporter;
import net.minecraft.data.server.recipe.RecipeProvider;
import net.minecraft.data.server.recipe.ShapelessRecipeJsonBuilder;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.recipe.book.RecipeCategory;
import net.minecraft.state.StateManager;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.BlockView;
import org.jetbrains.annotations.Nullable;
import static fr.adrien1106.reframed.block.ReFramedHalfStairBlock.HALF_STAIR_VOXELS;
import static fr.adrien1106.reframed.block.ReFramedStairBlock.getStairShape;
import static fr.adrien1106.reframed.util.blocks.BlockProperties.EDGE;
import static fr.adrien1106.reframed.util.blocks.Edge.*;
import static net.minecraft.data.client.VariantSettings.Rotation.*;
import static net.minecraft.util.shape.VoxelShapes.empty;
public class ReFramedHalfStairsStairBlock extends WaterloggableReFramedDoubleBlock implements BlockStateProvider {
public ReFramedHalfStairsStairBlock(Settings settings) {
super(settings);
setDefaultState(getDefaultState().with(EDGE, NORTH_DOWN));
}
@Override
public Object getModelCacheKey(BlockState state) {
return state.get(EDGE);
}
@Override
public int getModelStateCount() {
return 12;
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(builder.add(EDGE));
}
@Nullable
@Override
public BlockState getPlacementState(ItemPlacementContext ctx) {
return super.getPlacementState(ctx).with(EDGE, BlockHelper.getPlacementEdge(ctx));
}
@Override
public VoxelShape getCullingShape(BlockState state, BlockView view, BlockPos pos) {
return isGhost(view, pos) ? empty(): getStairShape(state.get(EDGE), StairShape.STRAIGHT);
}
@Override
public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return getStairShape(state.get(EDGE), StairShape.STRAIGHT);
}
@Override
public VoxelShape getShape(BlockState state, int i) {
Edge edge = state.get(EDGE);
Direction side = i == 1
? edge.getRightDirection()
: edge.getLeftDirection();
Corner corner = Corner.getByDirections(
edge.getFirstDirection(),
edge.getSecondDirection(),
side
);
return HALF_STAIR_VOXELS[corner.getID() * 3 + corner.getDirectionIndex(side)];
}
@Override
public int getTopThemeIndex(BlockState state) {
return 2;
}
@Override
public BlockStateSupplier getMultipart() {
Identifier model_id = ReFramed.id("half_stairs_stair_down_special");
Identifier side_model_id = ReFramed.id("half_stairs_stair_side_special");
Identifier reverse_model_id = ReFramed.id("half_stairs_stair_reverse_special");
return MultipartBlockStateSupplier.create(this)
/* X AXIS */
.with(GBlockstate.when(EDGE, NORTH_DOWN),
GBlockstate.variant(side_model_id, true, R90, R180))
.with(GBlockstate.when(EDGE, DOWN_SOUTH),
GBlockstate.variant(side_model_id, true, R0, R180))
.with(GBlockstate.when(EDGE, SOUTH_UP),
GBlockstate.variant(side_model_id, true, R270, R180))
.with(GBlockstate.when(EDGE, UP_NORTH),
GBlockstate.variant(side_model_id, true, R180, R180))
/* Y AXIS */
.with(GBlockstate.when(EDGE, NORTH_EAST),
GBlockstate.variant(model_id, true, R0, R0))
.with(GBlockstate.when(EDGE, EAST_SOUTH),
GBlockstate.variant(model_id, true, R0, R90))
.with(GBlockstate.when(EDGE, SOUTH_WEST),
GBlockstate.variant(model_id, true, R0, R180))
.with(GBlockstate.when(EDGE, WEST_NORTH),
GBlockstate.variant(model_id, true, R0, R270))
/* Z AXIS */
.with(GBlockstate.when(EDGE, DOWN_EAST),
GBlockstate.variant(side_model_id, true, R0, R90))
.with(GBlockstate.when(EDGE, EAST_UP),
GBlockstate.variant(reverse_model_id, true, R180, R270))
.with(GBlockstate.when(EDGE, UP_WEST),
GBlockstate.variant(side_model_id, true, R180, R90))
.with(GBlockstate.when(EDGE, WEST_DOWN),
GBlockstate.variant(reverse_model_id, true, R0, R270));
}
@Override
public void setRecipe(RecipeExporter exporter) {
RecipeProvider.offerStonecuttingRecipe(exporter, RecipeCategory.BUILDING_BLOCKS, this, ReFramed.CUBE, 2);
ShapelessRecipeJsonBuilder
.create(RecipeCategory.BUILDING_BLOCKS, this)
.input(ReFramed.HALF_STAIR, 2)
.criterion(FabricRecipeProvider.hasItem(ReFramed.CUBE), FabricRecipeProvider.conditionsFromItem(ReFramed.CUBE))
.criterion(FabricRecipeProvider.hasItem(this), FabricRecipeProvider.conditionsFromItem(this))
.offerTo(exporter);
}
}

View File

@@ -0,0 +1,117 @@
package fr.adrien1106.reframed.block;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.generator.GBlockstate;
import fr.adrien1106.reframed.util.VoxelHelper;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext;
import net.minecraft.data.client.MultipartBlockStateSupplier;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.state.StateManager;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.BlockView;
import org.jetbrains.annotations.Nullable;
import static fr.adrien1106.reframed.util.VoxelHelper.VoxelListBuilder;
import static net.minecraft.data.client.VariantSettings.Rotation.*;
import static net.minecraft.state.property.Properties.FACING;
import static net.minecraft.state.property.Properties.LAYERS;
public class ReFramedLayerBlock extends ReFramedSlabBlock {
public static final VoxelShape[] LAYER_VOXELS;
private record ModelCacheKey(Direction face, int layer) {}
public ReFramedLayerBlock(Settings settings) {
super(settings);
setDefaultState(getDefaultState().with(LAYERS, 1));
}
@Override
public Object getModelCacheKey(BlockState state) {
return new ModelCacheKey(state.get(FACING), state.get(LAYERS));
}
@Override
public int getModelStateCount() {
return 48;
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(builder.add(LAYERS));
}
@Override
public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return LAYER_VOXELS[state.get(FACING).getId() * 8 + state.get(LAYERS) - 1];
}
@Override
public boolean canReplace(BlockState state, ItemPlacementContext ctx) {
return !(!state.isOf(this) || ctx.getPlayer().isSneaking() || state.get(LAYERS) == 8);
}
@Override
public @Nullable BlockState getPlacementState(ItemPlacementContext ctx) {
BlockState previous = ctx.getWorld().getBlockState(ctx.getBlockPos());
if (!previous.isOf(this)) return super.getPlacementState(ctx);
return previous.with(LAYERS, previous.get(LAYERS) + 1);
}
@Override
public MultipartBlockStateSupplier getMultipart() {
String model_pattern = "layer_x_special";
MultipartBlockStateSupplier supplier = MultipartBlockStateSupplier.create(this);
for (int i = 1; i <= 8; i++) {
Identifier model = ReFramed.id(model_pattern.replace("x", i + ""));
supplier
.with(GBlockstate.when(FACING, Direction.DOWN, LAYERS, i),
GBlockstate.variant(model, true, R0, R0))
.with(GBlockstate.when(FACING, Direction.SOUTH, LAYERS, i),
GBlockstate.variant(model, true, R90, R0))
.with(GBlockstate.when(FACING, Direction.UP, LAYERS, i),
GBlockstate.variant(model, true, R180, R0))
.with(GBlockstate.when(FACING, Direction.NORTH, LAYERS, i),
GBlockstate.variant(model, true, R270, R0))
.with(GBlockstate.when(FACING, Direction.WEST, LAYERS, i),
GBlockstate.variant(model, true, R90, R90))
.with(GBlockstate.when(FACING, Direction.EAST, LAYERS, i),
GBlockstate.variant(model, true, R90, R270));
}
return supplier;
}
static {
VoxelListBuilder builder = VoxelListBuilder.create(createCuboidShape(0, 0, 0, 16, 2, 16), 48)
.add(createCuboidShape(0, 0, 0, 16, 4, 16))
.add(createCuboidShape(0, 0, 0, 16, 6, 16))
.add(createCuboidShape(0, 0, 0, 16, 8, 16))
.add(createCuboidShape(0, 0, 0, 16, 10, 16))
.add(createCuboidShape(0, 0, 0, 16, 12, 16))
.add(createCuboidShape(0, 0, 0, 16, 14, 16))
.add(createCuboidShape(0, 0, 0, 16, 16, 16));
for (int i = 0; i < 8; i++) {
builder.add(i, VoxelHelper::mirrorY);
}
for (int i = 0; i < 8; i++) {
builder.add(i, VoxelHelper::rotateX);
}
for (int i = 0; i < 8; i++) {
builder.add(i, VoxelHelper::rotateCX);
}
for (int i = 0; i < 8; i++) {
builder.add(i, VoxelHelper::rotateCZ);
}
for (int i = 0; i < 8; i++) {
builder.add(i, VoxelHelper::rotateZ);
}
LAYER_VOXELS = builder.build();
}
}

View File

@@ -1,90 +0,0 @@
package fr.adrien1106.reframed.block;
import com.google.common.base.MoreObjects;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.util.ReFramedInteractionUtil;
import net.minecraft.block.Block;
import net.minecraft.block.BlockEntityProvider;
import net.minecraft.block.BlockState;
import net.minecraft.block.LeverBlock;
import net.minecraft.block.ShapeContext;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.item.ItemStack;
import net.minecraft.state.StateManager;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
import org.jetbrains.annotations.Nullable;
public class ReFramedLeverBlock extends LeverBlock implements BlockEntityProvider {
public ReFramedLeverBlock(Settings settings) {
super(settings);
setDefaultState(ReFramedInteractionUtil.setDefaultStates(getDefaultState()));
}
@Nullable
@Override
public BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
return ReFramed.REFRAMED_BLOCK_ENTITY.instantiate(pos, state);
}
@Nullable
@Override
public BlockState getPlacementState(ItemPlacementContext ctx) {
return ReFramedInteractionUtil.modifyPlacementState(super.getPlacementState(ctx), ctx);
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(ReFramedInteractionUtil.appendProperties(builder));
}
@Override
public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
ActionResult r = ReFramedInteractionUtil.onUse(state, world, pos, player, hand, hit);
if(!r.isAccepted()) r = super.onUse(state, world, pos, player, hand, hit);
return r;
}
@Override
public void onStateReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) {
ReFramedInteractionUtil.onStateReplaced(state, world, pos, newState, moved);
super.onStateReplaced(state, world, pos, newState, moved);
}
@Override
public void onPlaced(World world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) {
ReFramedInteractionUtil.onPlaced(world, pos, state, placer, stack);
}
@Override
public VoxelShape getCollisionShape(BlockState state, BlockView view, BlockPos pos, ShapeContext ctx) {
return MoreObjects.firstNonNull(ReFramedInteractionUtil.getCollisionShape(state, view, pos, ctx), super.getCollisionShape(state, view, pos, ctx));
}
@Override
public boolean emitsRedstonePower(BlockState state) {
return super.emitsRedstonePower(state);
}
@Override
public int getWeakRedstonePower(BlockState state, BlockView view, BlockPos pos, Direction dir) {
boolean a = 0 != super.getWeakRedstonePower(state, view, pos, dir);
boolean b = 0 != ReFramedInteractionUtil.getWeakRedstonePower(state, view, pos, dir);
return (a ^ b) ? 15 : 0;
}
@Override
public int getStrongRedstonePower(BlockState state, BlockView view, BlockPos pos, Direction dir) {
if(getDirection(state) != dir) return 0;
else return getWeakRedstonePower(state, view, pos, dir);
}
}

View File

@@ -1,87 +0,0 @@
package fr.adrien1106.reframed.block;
import com.google.common.base.MoreObjects;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.util.ReFramedInteractionUtil;
import net.minecraft.block.Block;
import net.minecraft.block.BlockEntityProvider;
import net.minecraft.block.BlockState;
import net.minecraft.block.PaneBlock;
import net.minecraft.block.ShapeContext;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.item.ItemStack;
import net.minecraft.state.StateManager;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
import org.jetbrains.annotations.Nullable;
public class ReFramedPaneBlock extends PaneBlock implements BlockEntityProvider {
public ReFramedPaneBlock(Settings settings) {
super(settings);
setDefaultState(ReFramedInteractionUtil.setDefaultStates(getDefaultState()));
}
@Override
public @Nullable BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
return ReFramed.REFRAMED_BLOCK_ENTITY.instantiate(pos, state);
}
@Nullable
@Override
public BlockState getPlacementState(ItemPlacementContext ctx) {
return ReFramedInteractionUtil.modifyPlacementState(super.getPlacementState(ctx), ctx);
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(ReFramedInteractionUtil.appendProperties(builder));
}
@Override
public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
ActionResult r = ReFramedInteractionUtil.onUse(state, world, pos, player, hand, hit);
if(!r.isAccepted()) r = super.onUse(state, world, pos, player, hand, hit);
return r;
}
@Override
public void onStateReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) {
ReFramedInteractionUtil.onStateReplaced(state, world, pos, newState, moved);
super.onStateReplaced(state, world, pos, newState, moved);
}
@Override
public void onPlaced(World world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) {
ReFramedInteractionUtil.onPlaced(world, pos, state, placer, stack);
super.onPlaced(world, pos, state, placer, stack);
}
@Override
public VoxelShape getCollisionShape(BlockState state, BlockView view, BlockPos pos, ShapeContext ctx) {
return MoreObjects.firstNonNull(ReFramedInteractionUtil.getCollisionShape(state, view, pos, ctx), super.getCollisionShape(state, view, pos, ctx));
}
@Override
public boolean emitsRedstonePower(BlockState state) {
return ReFramedInteractionUtil.emitsRedstonePower(state);
}
@Override
public int getWeakRedstonePower(BlockState state, BlockView view, BlockPos pos, Direction dir) {
return ReFramedInteractionUtil.getWeakRedstonePower(state, view, pos, dir);
}
@Override
public int getStrongRedstonePower(BlockState state, BlockView view, BlockPos pos, Direction dir) {
return ReFramedInteractionUtil.getStrongRedstonePower(state, view, pos, dir);
}
}

View File

@@ -1,57 +0,0 @@
package fr.adrien1106.reframed.block;
import com.google.common.base.MoreObjects;
import fr.adrien1106.reframed.util.ReFramedInteractionUtil;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.state.StateManager;
import net.minecraft.state.property.Properties;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.BlockView;
import org.jetbrains.annotations.Nullable;
public class ReFramedPostBlock extends WaterloggableReFramedBlock {
public ReFramedPostBlock(Settings settings) {
super(settings);
setDefaultState(getDefaultState().with(Properties.AXIS, Direction.Axis.Y));
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(builder.add(Properties.AXIS));
}
@Override
public @Nullable BlockState getPlacementState(ItemPlacementContext ctx) {
BlockState sup = super.getPlacementState(ctx);
if(sup != null) sup = sup.with(Properties.AXIS, ctx.getSide().getAxis());
return sup;
}
protected static final VoxelShape SHAPE_X = createCuboidShape(0, 6, 6, 16, 10, 10);
protected static final VoxelShape SHAPE_Y = createCuboidShape(6, 0, 6, 10, 16, 10);
protected static final VoxelShape SHAPE_Z = createCuboidShape(6, 6, 0, 10, 10, 16);
protected VoxelShape shap(BlockState state) {
return switch(state.get(Properties.AXIS)) {
case X -> SHAPE_X;
case Y -> SHAPE_Y;
case Z -> SHAPE_Z;
};
}
@Override
public VoxelShape getCollisionShape(BlockState state, BlockView view, BlockPos pos, ShapeContext ctx) {
return MoreObjects.firstNonNull(ReFramedInteractionUtil.getCollisionShape(state, view, pos, ctx), shap(state));
}
@Override
public VoxelShape getOutlineShape(BlockState state, BlockView view, BlockPos pos, ShapeContext ctx) {
return shap(state);
}
}

View File

@@ -1,89 +0,0 @@
package fr.adrien1106.reframed.block;
import com.google.common.base.MoreObjects;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.util.ReFramedInteractionUtil;
import net.minecraft.block.*;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.item.ItemStack;
import net.minecraft.state.StateManager;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
import org.jetbrains.annotations.Nullable;
public class ReFramedPressurePlateBlock extends PressurePlateBlock implements BlockEntityProvider {
public ReFramedPressurePlateBlock(Settings settings, BlockSetType blockSetType) {
super(blockSetType, settings);
setDefaultState(ReFramedInteractionUtil.setDefaultStates(getDefaultState()));
}
public ReFramedPressurePlateBlock(Settings settings) {
this(settings, BlockSetType.OAK);
}
@Override
public @Nullable BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
return ReFramed.REFRAMED_BLOCK_ENTITY.instantiate(pos, state);
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(ReFramedInteractionUtil.appendProperties(builder));
}
@Nullable
@Override
public BlockState getPlacementState(ItemPlacementContext ctx) {
return ReFramedInteractionUtil.modifyPlacementState(super.getPlacementState(ctx), ctx);
}
@Override
public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
ActionResult r = ReFramedInteractionUtil.onUse(state, world, pos, player, hand, hit);
if(!r.isAccepted()) r = super.onUse(state, world, pos, player, hand, hit);
return r;
}
@Override
public void onStateReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) {
ReFramedInteractionUtil.onStateReplaced(state, world, pos, newState, moved);
super.onStateReplaced(state, world, pos, newState, moved);
}
@Override
public void onPlaced(World world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) {
ReFramedInteractionUtil.onPlaced(world, pos, state, placer, stack);
super.onPlaced(world, pos, state, placer, stack);
}
@Override
public VoxelShape getCollisionShape(BlockState state, BlockView view, BlockPos pos, ShapeContext ctx) {
return MoreObjects.firstNonNull(ReFramedInteractionUtil.getCollisionShape(state, view, pos, ctx), super.getCollisionShape(state, view, pos, ctx));
}
@Override
public boolean emitsRedstonePower(BlockState state) {
return super.emitsRedstonePower(state);
}
@Override
public int getWeakRedstonePower(BlockState state, BlockView view, BlockPos pos, Direction dir) {
boolean a = 0 != super.getWeakRedstonePower(state, view, pos, dir);
boolean b = 0 != ReFramedInteractionUtil.getWeakRedstonePower(state, view, pos, dir);
return (a ^ b) ? 15 : 0;
}
@Override
public int getStrongRedstonePower(BlockState state, BlockView view, BlockPos pos, Direction dir) {
return dir == Direction.UP ? getWeakRedstonePower(state, view, pos, dir) : 0;
}
}

View File

@@ -1,11 +1,20 @@
package fr.adrien1106.reframed.block;
import fr.adrien1106.reframed.generator.MultipartBlockStateProvider;
import net.minecraft.block.*;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.generator.BlockStateProvider;
import fr.adrien1106.reframed.generator.GBlockstate;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext;
import net.minecraft.data.client.MultipartBlockStateSupplier;
import net.minecraft.data.server.recipe.RecipeExporter;
import net.minecraft.data.server.recipe.RecipeProvider;
import net.minecraft.data.server.recipe.ShapedRecipeJsonBuilder;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.recipe.book.RecipeCategory;
import net.minecraft.state.StateManager;
import net.minecraft.state.property.Properties;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.shape.VoxelShape;
@@ -13,34 +22,51 @@ import net.minecraft.util.shape.VoxelShapes;
import net.minecraft.world.BlockView;
import org.jetbrains.annotations.Nullable;
public class ReFramedSlabBlock extends WaterloggableReFramedBlock implements MultipartBlockStateProvider {
import static net.minecraft.data.client.VariantSettings.Rotation.*;
import static net.minecraft.state.property.Properties.FACING;
private static final VoxelShape DOWN = VoxelShapes.cuboid(0f, 0f, 0f, 1f, 0.5f, 1f);
private static final VoxelShape UP = VoxelShapes.cuboid(0f, 0.5f, 0f, 1f, 1f, 1f);
private static final VoxelShape NORTH = VoxelShapes.cuboid(0f, 0f, 0f, 1f, 1f, 0.5f);
private static final VoxelShape SOUTH = VoxelShapes.cuboid(0f, 0f, 0.5f, 1f, 1f, 1f);
private static final VoxelShape EAST = VoxelShapes.cuboid(0.5f, 0f, 0f, 1f, 1f, 1f);
private static final VoxelShape WEST = VoxelShapes.cuboid(0f, 0f, 0f, 0.5f, 1f, 1f);
public class ReFramedSlabBlock extends WaterloggableReFramedBlock implements BlockStateProvider {
protected static final VoxelShape DOWN = VoxelShapes.cuboid(0f, 0f, 0f, 1f, 0.5f, 1f);
protected static final VoxelShape UP = VoxelShapes.cuboid(0f, 0.5f, 0f, 1f, 1f, 1f);
protected static final VoxelShape NORTH = VoxelShapes.cuboid(0f, 0f, 0f, 1f, 1f, 0.5f);
protected static final VoxelShape SOUTH = VoxelShapes.cuboid(0f, 0f, 0.5f, 1f, 1f, 1f);
protected static final VoxelShape EAST = VoxelShapes.cuboid(0.5f, 0f, 0f, 1f, 1f, 1f);
protected static final VoxelShape WEST = VoxelShapes.cuboid(0f, 0f, 0f, 0.5f, 1f, 1f);
public ReFramedSlabBlock(Settings settings) {
super(settings);
setDefaultState(getDefaultState().with(Properties.FACING, Direction.DOWN));
setDefaultState(getDefaultState().with(FACING, Direction.DOWN));
}
@Override
public Object getModelCacheKey(BlockState state) {
return state.get(FACING);
}
@Override
public int getModelStateCount() {
return 6;
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(builder.add(Properties.FACING));
super.appendProperties(builder.add(FACING));
}
@Nullable
@Override
public BlockState getPlacementState(ItemPlacementContext ctx) {
return super.getPlacementState(ctx).with(Properties.FACING, ctx.getSide().getOpposite());
return super.getPlacementState(ctx).with(FACING, ctx.getSide().getOpposite());
}
@Override
public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return switch (state.get(Properties.FACING)) {
return getSlabShape(state.get(FACING));
}
public static VoxelShape getSlabShape(Direction side) {
return switch (side) {
case DOWN -> DOWN;
case UP -> UP;
case NORTH -> NORTH;
@@ -52,6 +78,31 @@ public class ReFramedSlabBlock extends WaterloggableReFramedBlock implements Mul
@Override
public MultipartBlockStateSupplier getMultipart() {
return null;
Identifier model_id = ReFramed.id("slab_special");
return MultipartBlockStateSupplier.create(this)
.with(GBlockstate.when(FACING, Direction.DOWN),
GBlockstate.variant(model_id, true, R0, R0))
.with(GBlockstate.when(FACING, Direction.SOUTH),
GBlockstate.variant(model_id, true, R90, R0))
.with(GBlockstate.when(FACING, Direction.UP),
GBlockstate.variant(model_id, true, R180, R0))
.with(GBlockstate.when(FACING, Direction.NORTH),
GBlockstate.variant(model_id, true, R270, R0))
.with(GBlockstate.when(FACING, Direction.WEST),
GBlockstate.variant(model_id, true, R90, R90))
.with(GBlockstate.when(FACING, Direction.EAST),
GBlockstate.variant(model_id, true, R90, R270));
}
@Override
public void setRecipe(RecipeExporter exporter) {
RecipeProvider.offerStonecuttingRecipe(exporter, RecipeCategory.BUILDING_BLOCKS, this, ReFramed.CUBE, 2);
ShapedRecipeJsonBuilder
.create(RecipeCategory.BUILDING_BLOCKS, this, 6)
.pattern("III")
.input('I', ReFramed.CUBE)
.criterion(FabricRecipeProvider.hasItem(ReFramed.CUBE), FabricRecipeProvider.conditionsFromItem(ReFramed.CUBE))
.criterion(FabricRecipeProvider.hasItem(this), FabricRecipeProvider.conditionsFromItem(this))
.offerTo(exporter);
}
}

View File

@@ -0,0 +1,91 @@
package fr.adrien1106.reframed.block;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.generator.BlockStateProvider;
import fr.adrien1106.reframed.generator.GBlockstate;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.data.client.MultipartBlockStateSupplier;
import net.minecraft.data.server.recipe.RecipeExporter;
import net.minecraft.data.server.recipe.RecipeProvider;
import net.minecraft.data.server.recipe.ShapelessRecipeJsonBuilder;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.recipe.book.RecipeCategory;
import net.minecraft.state.StateManager;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.Direction;
import net.minecraft.util.shape.VoxelShape;
import org.jetbrains.annotations.Nullable;
import static fr.adrien1106.reframed.block.ReFramedSlabBlock.*;
import static net.minecraft.data.client.VariantSettings.Rotation.R0;
import static net.minecraft.data.client.VariantSettings.Rotation.R90;
import static net.minecraft.state.property.Properties.AXIS;
public class ReFramedSlabsCubeBlock extends ReFramedDoubleBlock implements BlockStateProvider {
public ReFramedSlabsCubeBlock(Settings settings) {
super(settings);
setDefaultState(getDefaultState().with(AXIS, Direction.Axis.Y));
}
@Override
public Object getModelCacheKey(BlockState state) {
return state.get(AXIS);
}
@Override
public int getModelStateCount() {
return 3;
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(builder.add(AXIS));
}
@Nullable
@Override
public BlockState getPlacementState(ItemPlacementContext ctx) {
return super.getPlacementState(ctx).with(AXIS, ctx.getSide().getAxis());
}
@Override
public VoxelShape getShape(BlockState state, int i) {
return switch (state.get(AXIS)) {
case Y -> i == 2 ? UP : DOWN;
case Z -> i == 2 ? NORTH : SOUTH;
case X -> i == 2 ? EAST : WEST;
};
}
@Override
public int getTopThemeIndex(BlockState state) {
// when the side is shared just return one
return state.get(AXIS) == Direction.Axis.Y ? 2: super.getTopThemeIndex(state);
}
@Override
public MultipartBlockStateSupplier getMultipart() {
Identifier model_id = ReFramed.id("double_slab_special");
return MultipartBlockStateSupplier.create(this)
.with(GBlockstate.when(AXIS, Direction.Axis.Y),
GBlockstate.variant(model_id, true, R0, R0))
.with(GBlockstate.when(AXIS, Direction.Axis.Z),
GBlockstate.variant(model_id, true, R90, R0))
.with(GBlockstate.when(AXIS, Direction.Axis.X),
GBlockstate.variant(model_id, true, R90, R90));
}
@Override
public void setRecipe(RecipeExporter exporter) {
RecipeProvider.offerStonecuttingRecipe(exporter, RecipeCategory.BUILDING_BLOCKS, this, ReFramed.CUBE);
ShapelessRecipeJsonBuilder
.create(RecipeCategory.BUILDING_BLOCKS, this)
.input(ReFramed.SLAB, 2)
.criterion(FabricRecipeProvider.hasItem(ReFramed.CUBE), FabricRecipeProvider.conditionsFromItem(ReFramed.CUBE))
.criterion(FabricRecipeProvider.hasItem(this), FabricRecipeProvider.conditionsFromItem(this))
.offerTo(exporter);
}
}

View File

@@ -0,0 +1,113 @@
package fr.adrien1106.reframed.block;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.generator.BlockStateProvider;
import fr.adrien1106.reframed.generator.GBlockstate;
import fr.adrien1106.reframed.util.VoxelHelper;
import fr.adrien1106.reframed.util.blocks.BlockHelper;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext;
import net.minecraft.data.client.BlockStateSupplier;
import net.minecraft.data.client.MultipartBlockStateSupplier;
import net.minecraft.data.server.recipe.RecipeExporter;
import net.minecraft.data.server.recipe.RecipeProvider;
import net.minecraft.data.server.recipe.ShapelessRecipeJsonBuilder;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.recipe.book.RecipeCategory;
import net.minecraft.state.StateManager;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes;
import net.minecraft.world.BlockView;
import org.jetbrains.annotations.Nullable;
import static fr.adrien1106.reframed.util.VoxelHelper.VoxelListBuilder;
import static fr.adrien1106.reframed.util.blocks.BlockProperties.CORNER;
import static fr.adrien1106.reframed.util.blocks.Corner.*;
import static net.minecraft.data.client.VariantSettings.Rotation.*;
public class ReFramedSmallCubeBlock extends WaterloggableReFramedBlock implements BlockStateProvider {
public static final VoxelShape[] SMALL_CUBE_VOXELS;
public ReFramedSmallCubeBlock(Settings settings) {
super(settings);
setDefaultState(getDefaultState().with(CORNER, NORTH_EAST_DOWN));
}
@Override
public Object getModelCacheKey(BlockState state) {
return state.get(CORNER);
}
@Override
public int getModelStateCount() {
return 8;
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(builder.add(CORNER));
}
@Override
public @Nullable BlockState getPlacementState(ItemPlacementContext ctx) {
return super.getPlacementState(ctx).with(CORNER, BlockHelper.getPlacementCorner(ctx));
}
@Override
public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return SMALL_CUBE_VOXELS[state.get(CORNER).getID()];
}
@Override
public BlockStateSupplier getMultipart() {
Identifier small_cube_id = ReFramed.id("small_cube_special");
return MultipartBlockStateSupplier.create(this)
.with(GBlockstate.when(CORNER, NORTH_EAST_DOWN),
GBlockstate.variant(small_cube_id, true, R0, R0))
.with(GBlockstate.when(CORNER, EAST_SOUTH_DOWN),
GBlockstate.variant(small_cube_id, true, R0, R90))
.with(GBlockstate.when(CORNER, SOUTH_WEST_DOWN),
GBlockstate.variant(small_cube_id, true, R0, R180))
.with(GBlockstate.when(CORNER, WEST_NORTH_DOWN),
GBlockstate.variant(small_cube_id, true, R0, R270))
.with(GBlockstate.when(CORNER, EAST_SOUTH_UP),
GBlockstate.variant(small_cube_id, true, R180, R0))
.with(GBlockstate.when(CORNER, SOUTH_WEST_UP),
GBlockstate.variant(small_cube_id, true, R180, R90))
.with(GBlockstate.when(CORNER, WEST_NORTH_UP),
GBlockstate.variant(small_cube_id, true, R180, R180))
.with(GBlockstate.when(CORNER, NORTH_EAST_UP),
GBlockstate.variant(small_cube_id, true, R180, R270));
}
@Override
public void setRecipe(RecipeExporter exporter) {
RecipeProvider.offerStonecuttingRecipe(exporter, RecipeCategory.BUILDING_BLOCKS, this, ReFramed.CUBE, 8);
ShapelessRecipeJsonBuilder
.create(RecipeCategory.BUILDING_BLOCKS, this, 8)
.input(ReFramed.CUBE)
.criterion(FabricRecipeProvider.hasItem(ReFramed.CUBE), FabricRecipeProvider.conditionsFromItem(ReFramed.CUBE))
.criterion(FabricRecipeProvider.hasItem(this), FabricRecipeProvider.conditionsFromItem(this))
.offerTo(exporter);
}
static {
final VoxelShape SMALL_CUBE = VoxelShapes.cuboid(.5f, 0f, 0f, 1f, .5f, .5f);
SMALL_CUBE_VOXELS = VoxelListBuilder.create(SMALL_CUBE, 8)
.add(VoxelHelper::rotateY)
.add(VoxelHelper::rotateY)
.add(VoxelHelper::rotateY)
.add(SMALL_CUBE, VoxelHelper::mirrorY)
.add(VoxelHelper::rotateY)
.add(VoxelHelper::rotateY)
.add(VoxelHelper::rotateY)
.build();
}
}

View File

@@ -0,0 +1,133 @@
package fr.adrien1106.reframed.block;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.generator.BlockStateProvider;
import fr.adrien1106.reframed.generator.GBlockstate;
import fr.adrien1106.reframed.util.blocks.BlockHelper;
import fr.adrien1106.reframed.util.blocks.Corner;
import fr.adrien1106.reframed.util.blocks.Edge;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext;
import net.minecraft.data.client.BlockStateSupplier;
import net.minecraft.data.client.MultipartBlockStateSupplier;
import net.minecraft.data.server.recipe.RecipeExporter;
import net.minecraft.data.server.recipe.RecipeProvider;
import net.minecraft.data.server.recipe.ShapelessRecipeJsonBuilder;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.recipe.book.RecipeCategory;
import net.minecraft.state.StateManager;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.BlockView;
import org.jetbrains.annotations.Nullable;
import static fr.adrien1106.reframed.block.ReFramedSmallCubeBlock.SMALL_CUBE_VOXELS;
import static fr.adrien1106.reframed.block.ReFramedStepBlock.getStepShape;
import static fr.adrien1106.reframed.util.blocks.BlockProperties.EDGE;
import static fr.adrien1106.reframed.util.blocks.Edge.*;
import static net.minecraft.data.client.VariantSettings.Rotation.*;
import static net.minecraft.util.shape.VoxelShapes.empty;
public class ReFramedSmallCubesStepBlock extends WaterloggableReFramedDoubleBlock implements BlockStateProvider {
public ReFramedSmallCubesStepBlock(Settings settings) {
super(settings);
setDefaultState(getDefaultState().with(EDGE, Edge.NORTH_DOWN));
}
@Override
public Object getModelCacheKey(BlockState state) {
return state.get(EDGE);
}
@Override
public int getModelStateCount() {
return 12;
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(builder.add(EDGE));
}
@Nullable
@Override
public BlockState getPlacementState(ItemPlacementContext ctx) {
return super.getPlacementState(ctx).with(EDGE, BlockHelper.getPlacementEdge(ctx));
}
@Override
public VoxelShape getCullingShape(BlockState state, BlockView view, BlockPos pos) {
return isGhost(view, pos) ? empty(): getStepShape(state.get(EDGE));
}
@Override
public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return getStepShape(state.get(EDGE));
}
@Override
public VoxelShape getShape(BlockState state, int i) {
Edge edge = state.get(EDGE);
return SMALL_CUBE_VOXELS[Corner.getByDirections(
edge.getFirstDirection(),
edge.getSecondDirection(),
i == 1
? edge.getRightDirection()
: edge.getLeftDirection()
).getID()];
}
@Override
public int getTopThemeIndex(BlockState state) {
return 2;
}
@Override
public BlockStateSupplier getMultipart() {
Identifier model_id = ReFramed.id("small_cubes_step_special");
Identifier reverse_model_id = ReFramed.id("small_cubes_step_reverse_special");
return MultipartBlockStateSupplier.create(this)
/* X AXIS */
.with(GBlockstate.when(EDGE, DOWN_EAST),
GBlockstate.variant(reverse_model_id, true, R0, R0))
.with(GBlockstate.when(EDGE, EAST_UP),
GBlockstate.variant(model_id, true, R180, R0))
.with(GBlockstate.when(EDGE, UP_WEST),
GBlockstate.variant(reverse_model_id, true, R180, R180))
.with(GBlockstate.when(EDGE, WEST_DOWN),
GBlockstate.variant(model_id, true, R0, R180))
/* Y AXIS */
.with(GBlockstate.when(EDGE, EAST_SOUTH),
GBlockstate.variant(model_id, true, R90, R0))
.with(GBlockstate.when(EDGE, SOUTH_WEST),
GBlockstate.variant(model_id, true, R90, R90))
.with(GBlockstate.when(EDGE, WEST_NORTH),
GBlockstate.variant(model_id, true, R90, R180))
.with(GBlockstate.when(EDGE, NORTH_EAST),
GBlockstate.variant(model_id, true, R90, R270))
/* Z AXIS */
.with(GBlockstate.when(EDGE, DOWN_SOUTH),
GBlockstate.variant(reverse_model_id, true, R0, R90))
.with(GBlockstate.when(EDGE, NORTH_DOWN),
GBlockstate.variant(model_id, true, R0, R270))
.with(GBlockstate.when(EDGE, UP_NORTH),
GBlockstate.variant(reverse_model_id, true, R180, R270))
.with(GBlockstate.when(EDGE, SOUTH_UP),
GBlockstate.variant(model_id, true, R180, R90));
}
@Override
public void setRecipe(RecipeExporter exporter) {
RecipeProvider.offerStonecuttingRecipe(exporter, RecipeCategory.BUILDING_BLOCKS, this, ReFramed.CUBE, 4);
ShapelessRecipeJsonBuilder
.create(RecipeCategory.BUILDING_BLOCKS, this)
.input(ReFramed.SMALL_CUBE, 2)
.criterion(FabricRecipeProvider.hasItem(ReFramed.CUBE), FabricRecipeProvider.conditionsFromItem(ReFramed.CUBE))
.criterion(FabricRecipeProvider.hasItem(this), FabricRecipeProvider.conditionsFromItem(this))
.offerTo(exporter);
}
}

View File

@@ -0,0 +1,433 @@
package fr.adrien1106.reframed.block;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.generator.BlockStateProvider;
import fr.adrien1106.reframed.generator.GBlockstate;
import fr.adrien1106.reframed.util.VoxelHelper;
import fr.adrien1106.reframed.util.blocks.BlockHelper;
import fr.adrien1106.reframed.util.blocks.Edge;
import fr.adrien1106.reframed.util.blocks.StairShape;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext;
import net.minecraft.data.client.MultipartBlockStateSupplier;
import net.minecraft.data.client.When;
import net.minecraft.data.server.recipe.RecipeExporter;
import net.minecraft.data.server.recipe.RecipeProvider;
import net.minecraft.data.server.recipe.ShapedRecipeJsonBuilder;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.recipe.book.RecipeCategory;
import net.minecraft.state.StateManager;
import net.minecraft.util.Identifier;
import net.minecraft.util.function.BooleanBiFunction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
import net.minecraft.world.WorldAccess;
import org.jetbrains.annotations.Nullable;
import java.util.stream.Stream;
import static fr.adrien1106.reframed.util.VoxelHelper.VoxelListBuilder;
import static fr.adrien1106.reframed.util.blocks.BlockProperties.EDGE;
import static fr.adrien1106.reframed.util.blocks.BlockProperties.STAIR_SHAPE;
import static fr.adrien1106.reframed.util.blocks.Edge.*;
import static fr.adrien1106.reframed.util.blocks.StairShape.*;
import static net.minecraft.data.client.VariantSettings.Rotation.*;
public class ReFramedStairBlock extends WaterloggableReFramedBlock implements BlockStateProvider {
public static final VoxelShape[] STAIR_VOXELS;
private record ModelCacheKey(Edge edge, StairShape shape) {}
public ReFramedStairBlock(Settings settings) {
super(settings);
setDefaultState(getDefaultState().with(EDGE, Edge.NORTH_DOWN).with(STAIR_SHAPE, STRAIGHT));
}
@Override
public Object getModelCacheKey(BlockState state) {
return new ModelCacheKey(state.get(EDGE), state.get(STAIR_SHAPE));
}
@Override
public int getModelStateCount() {
return 108; // Has 12 * 9 state combination and 52 models still reduces cache size
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(builder.add(EDGE, STAIR_SHAPE));
}
@Override
public BlockState getStateForNeighborUpdate(BlockState state, Direction direction, BlockState neighbor_state, WorldAccess world, BlockPos pos, BlockPos moved) {
return super.getStateForNeighborUpdate(state, direction, neighbor_state, world, pos, moved)
.with(STAIR_SHAPE, BlockHelper.getStairsShape(state.get(EDGE), world, pos));
}
@Nullable
@Override // Pretty happy of how clean it is (also got it on first try :) )
public BlockState getPlacementState(ItemPlacementContext ctx) {
Edge face = BlockHelper.getPlacementEdge(ctx);
StairShape shape = BlockHelper.getStairsShape(face, ctx.getWorld(), ctx.getBlockPos());
return super.getPlacementState(ctx).with(EDGE, face).with(STAIR_SHAPE, shape);
}
@Override
public void onStateReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) {
super.onStateReplaced(state, world, pos, newState, moved);
if(!state.isOf(newState.getBlock())) world.removeBlockEntity(pos);
}
@Override
public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return getStairShape(state.get(EDGE), state.get(STAIR_SHAPE));
}
public static VoxelShape getStairShape(Edge edge, StairShape shape) {
return STAIR_VOXELS[edge.getID() * 9 + shape.getID()];
}
@Override
public MultipartBlockStateSupplier getMultipart() {
return getStairMultipart(this, false);
}
public static MultipartBlockStateSupplier getStairMultipart(Block block, boolean is_double) {
String infix = is_double ? "s_cube" : "";
Identifier straight_id = ReFramed.id("stair" + infix + "_special");
Identifier double_outer_id = ReFramed.id("outers_stair" + infix + "_special");
Identifier inner_id = ReFramed.id("inner_stair" + infix + "_special");
Identifier outer_id = ReFramed.id("outer_stair" + infix + "_special");
Identifier outer_side_id = ReFramed.id("outer_side_stair" + infix + "_special");
return MultipartBlockStateSupplier.create(block)
/* STRAIGHT X AXIS */
.with(GBlockstate.when(EDGE, DOWN_EAST, STAIR_SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R0, R0))
.with(GBlockstate.when(EDGE, EAST_UP, STAIR_SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R180, R0))
.with(GBlockstate.when(EDGE, UP_WEST, STAIR_SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R180, R180))
.with(GBlockstate.when(EDGE, WEST_DOWN, STAIR_SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R0, R180))
/* STRAIGHT Y AXIS */
.with(GBlockstate.when(EDGE, EAST_SOUTH, STAIR_SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R90, R0))
.with(GBlockstate.when(EDGE, SOUTH_WEST, STAIR_SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R90, R90))
.with(GBlockstate.when(EDGE, WEST_NORTH, STAIR_SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R90, R180))
.with(GBlockstate.when(EDGE, NORTH_EAST, STAIR_SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R90, R270))
/* STRAIGHT Z AXIS */
.with(GBlockstate.when(EDGE, DOWN_SOUTH, STAIR_SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R0, R90))
.with(GBlockstate.when(EDGE, NORTH_DOWN, STAIR_SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R0, R270))
.with(GBlockstate.when(EDGE, UP_NORTH, STAIR_SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R180, R270))
.with(GBlockstate.when(EDGE, SOUTH_UP, STAIR_SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R180, R90))
/* INNER BOTTOM */
.with(When.anyOf(
GBlockstate.when(EDGE, NORTH_DOWN, STAIR_SHAPE, INNER_RIGHT),
GBlockstate.when(EDGE, WEST_NORTH, STAIR_SHAPE, INNER_RIGHT),
GBlockstate.when(EDGE, WEST_DOWN, STAIR_SHAPE, INNER_LEFT)),
GBlockstate.variant(inner_id, true, R0, R180))
.with(When.anyOf(
GBlockstate.when(EDGE, NORTH_DOWN, STAIR_SHAPE, INNER_LEFT),
GBlockstate.when(EDGE, NORTH_EAST, STAIR_SHAPE, INNER_RIGHT),
GBlockstate.when(EDGE, DOWN_EAST, STAIR_SHAPE, INNER_LEFT)),
GBlockstate.variant(inner_id, true, R0, R270))
.with(When.anyOf(
GBlockstate.when(EDGE, DOWN_SOUTH, STAIR_SHAPE, INNER_LEFT),
GBlockstate.when(EDGE, EAST_SOUTH, STAIR_SHAPE, INNER_RIGHT),
GBlockstate.when(EDGE, DOWN_EAST, STAIR_SHAPE, INNER_RIGHT)),
GBlockstate.variant(inner_id, true, R0, R0))
.with(When.anyOf(
GBlockstate.when(EDGE, DOWN_SOUTH, STAIR_SHAPE, INNER_RIGHT),
GBlockstate.when(EDGE, SOUTH_WEST, STAIR_SHAPE, INNER_RIGHT),
GBlockstate.when(EDGE, WEST_DOWN, STAIR_SHAPE, INNER_RIGHT)),
GBlockstate.variant(inner_id, true, R0, R90))
/* INNER TOP */
.with(When.anyOf(
GBlockstate.when(EDGE, EAST_UP, STAIR_SHAPE, INNER_LEFT),
GBlockstate.when(EDGE, NORTH_EAST, STAIR_SHAPE, INNER_LEFT),
GBlockstate.when(EDGE, UP_NORTH, STAIR_SHAPE, INNER_LEFT)),
GBlockstate.variant(inner_id, true, R180, R0))
.with(When.anyOf(
GBlockstate.when(EDGE, EAST_UP, STAIR_SHAPE, INNER_RIGHT),
GBlockstate.when(EDGE, EAST_SOUTH, STAIR_SHAPE, INNER_LEFT),
GBlockstate.when(EDGE, SOUTH_UP, STAIR_SHAPE, INNER_LEFT)),
GBlockstate.variant(inner_id, true, R180, R90))
.with(When.anyOf(
GBlockstate.when(EDGE, SOUTH_UP, STAIR_SHAPE, INNER_RIGHT),
GBlockstate.when(EDGE, SOUTH_WEST, STAIR_SHAPE, INNER_LEFT),
GBlockstate.when(EDGE, UP_WEST, STAIR_SHAPE, INNER_RIGHT)),
GBlockstate.variant(inner_id, true, R180, R180))
.with(When.anyOf(
GBlockstate.when(EDGE, UP_NORTH, STAIR_SHAPE, INNER_RIGHT),
GBlockstate.when(EDGE, WEST_NORTH, STAIR_SHAPE, INNER_LEFT),
GBlockstate.when(EDGE, UP_WEST, STAIR_SHAPE, INNER_LEFT)),
GBlockstate.variant(inner_id, true, R180, R270))
/* OUTER BOTTOM */
.with(When.anyOf(
GBlockstate.when(EDGE, DOWN_SOUTH, STAIR_SHAPE, SECOND_OUTER_LEFT),
GBlockstate.when(EDGE, DOWN_EAST, STAIR_SHAPE, SECOND_OUTER_RIGHT)),
GBlockstate.variant(outer_id, true, R0, R0))
.with(When.anyOf(
GBlockstate.when(EDGE, DOWN_SOUTH, STAIR_SHAPE, SECOND_OUTER_RIGHT),
GBlockstate.when(EDGE, WEST_DOWN, STAIR_SHAPE, FIRST_OUTER_RIGHT)),
GBlockstate.variant(outer_id, true, R0, R90))
.with(When.anyOf(
GBlockstate.when(EDGE, NORTH_DOWN, STAIR_SHAPE, FIRST_OUTER_RIGHT),
GBlockstate.when(EDGE, WEST_DOWN, STAIR_SHAPE, FIRST_OUTER_LEFT)),
GBlockstate.variant(outer_id, true, R0, R180))
.with(When.anyOf(
GBlockstate.when(EDGE, NORTH_DOWN, STAIR_SHAPE, FIRST_OUTER_LEFT),
GBlockstate.when(EDGE, DOWN_EAST, STAIR_SHAPE, SECOND_OUTER_LEFT)),
GBlockstate.variant(outer_id, true, R0, R270))
/* OUTER TOP */
.with(When.anyOf(
GBlockstate.when(EDGE, UP_NORTH, STAIR_SHAPE, SECOND_OUTER_LEFT),
GBlockstate.when(EDGE, EAST_UP, STAIR_SHAPE, FIRST_OUTER_LEFT)),
GBlockstate.variant(outer_id, true, R180, R0))
.with(When.anyOf(
GBlockstate.when(EDGE, SOUTH_UP, STAIR_SHAPE, FIRST_OUTER_LEFT),
GBlockstate.when(EDGE, EAST_UP, STAIR_SHAPE, FIRST_OUTER_RIGHT)),
GBlockstate.variant(outer_id, true, R180, R90))
.with(When.anyOf(
GBlockstate.when(EDGE, SOUTH_UP, STAIR_SHAPE, FIRST_OUTER_RIGHT),
GBlockstate.when(EDGE, UP_WEST, STAIR_SHAPE, SECOND_OUTER_RIGHT)),
GBlockstate.variant(outer_id, true, R180, R180))
.with(When.anyOf(
GBlockstate.when(EDGE, UP_NORTH, STAIR_SHAPE, SECOND_OUTER_RIGHT),
GBlockstate.when(EDGE, UP_WEST, STAIR_SHAPE, SECOND_OUTER_LEFT)),
GBlockstate.variant(outer_id, true, R180, R270))
/* OUTER EAST */
.with(When.anyOf(
GBlockstate.when(EDGE, EAST_SOUTH, STAIR_SHAPE, SECOND_OUTER_RIGHT),
GBlockstate.when(EDGE, DOWN_EAST, STAIR_SHAPE, FIRST_OUTER_RIGHT)),
GBlockstate.variant(outer_side_id, true, R0, R0))
.with(When.anyOf(
GBlockstate.when(EDGE, EAST_SOUTH, STAIR_SHAPE, SECOND_OUTER_LEFT),
GBlockstate.when(EDGE, EAST_UP, STAIR_SHAPE, SECOND_OUTER_RIGHT)),
GBlockstate.variant(outer_side_id, true, R90, R0))
.with(When.anyOf(
GBlockstate.when(EDGE, NORTH_EAST, STAIR_SHAPE, FIRST_OUTER_LEFT),
GBlockstate.when(EDGE, EAST_UP, STAIR_SHAPE, SECOND_OUTER_LEFT)),
GBlockstate.variant(outer_side_id, true, R180, R0))
.with(When.anyOf(
GBlockstate.when(EDGE, NORTH_EAST, STAIR_SHAPE, FIRST_OUTER_RIGHT),
GBlockstate.when(EDGE, DOWN_EAST, STAIR_SHAPE, FIRST_OUTER_LEFT)),
GBlockstate.variant(outer_side_id, true, R270, R0))
/* OUTER SOUTH */
.with(When.anyOf(
GBlockstate.when(EDGE, SOUTH_WEST, STAIR_SHAPE, SECOND_OUTER_RIGHT),
GBlockstate.when(EDGE, DOWN_SOUTH, STAIR_SHAPE, FIRST_OUTER_RIGHT)),
GBlockstate.variant(outer_side_id, true, R0, R90))
.with(When.anyOf(
GBlockstate.when(EDGE, SOUTH_WEST, STAIR_SHAPE, SECOND_OUTER_LEFT),
GBlockstate.when(EDGE, SOUTH_UP, STAIR_SHAPE, SECOND_OUTER_RIGHT)),
GBlockstate.variant(outer_side_id, true, R90, R90))
.with(When.anyOf(
GBlockstate.when(EDGE, EAST_SOUTH, STAIR_SHAPE, FIRST_OUTER_LEFT),
GBlockstate.when(EDGE, SOUTH_UP, STAIR_SHAPE, SECOND_OUTER_LEFT)),
GBlockstate.variant(outer_side_id, true, R180, R90))
.with(When.anyOf(
GBlockstate.when(EDGE, EAST_SOUTH, STAIR_SHAPE, FIRST_OUTER_RIGHT),
GBlockstate.when(EDGE, DOWN_SOUTH, STAIR_SHAPE, FIRST_OUTER_LEFT)),
GBlockstate.variant(outer_side_id, true, R270, R90))
/* OUTER WEST */
.with(When.anyOf(
GBlockstate.when(EDGE, WEST_NORTH, STAIR_SHAPE, SECOND_OUTER_RIGHT),
GBlockstate.when(EDGE, WEST_DOWN, STAIR_SHAPE, SECOND_OUTER_LEFT)),
GBlockstate.variant(outer_side_id, true, R0, R180))
.with(When.anyOf(
GBlockstate.when(EDGE, WEST_NORTH, STAIR_SHAPE, SECOND_OUTER_LEFT),
GBlockstate.when(EDGE, UP_WEST, STAIR_SHAPE, FIRST_OUTER_LEFT)),
GBlockstate.variant(outer_side_id, true, R90, R180))
.with(When.anyOf(
GBlockstate.when(EDGE, SOUTH_WEST, STAIR_SHAPE, FIRST_OUTER_LEFT),
GBlockstate.when(EDGE, UP_WEST, STAIR_SHAPE, FIRST_OUTER_RIGHT)),
GBlockstate.variant(outer_side_id, true, R180, R180))
.with(When.anyOf(
GBlockstate.when(EDGE, SOUTH_WEST, STAIR_SHAPE, FIRST_OUTER_RIGHT),
GBlockstate.when(EDGE, WEST_DOWN, STAIR_SHAPE, SECOND_OUTER_RIGHT)),
GBlockstate.variant(outer_side_id, true, R270, R180))
/* OUTER NORTH */
.with(When.anyOf(
GBlockstate.when(EDGE, NORTH_EAST, STAIR_SHAPE, SECOND_OUTER_RIGHT),
GBlockstate.when(EDGE, NORTH_DOWN, STAIR_SHAPE, SECOND_OUTER_LEFT)),
GBlockstate.variant(outer_side_id, true, R0, R270))
.with(When.anyOf(
GBlockstate.when(EDGE, NORTH_EAST, STAIR_SHAPE, SECOND_OUTER_LEFT),
GBlockstate.when(EDGE, UP_NORTH, STAIR_SHAPE, FIRST_OUTER_LEFT)),
GBlockstate.variant(outer_side_id, true, R90, R270))
.with(When.anyOf(
GBlockstate.when(EDGE, WEST_NORTH, STAIR_SHAPE, FIRST_OUTER_LEFT),
GBlockstate.when(EDGE, UP_NORTH, STAIR_SHAPE, FIRST_OUTER_RIGHT)),
GBlockstate.variant(outer_side_id, true, R180, R270))
.with(When.anyOf(
GBlockstate.when(EDGE, WEST_NORTH, STAIR_SHAPE, FIRST_OUTER_RIGHT),
GBlockstate.when(EDGE, NORTH_DOWN, STAIR_SHAPE, SECOND_OUTER_RIGHT)),
GBlockstate.variant(outer_side_id, true, R270, R270))
/* OUTER BOTTOM */
.with(When.anyOf(
GBlockstate.when(EDGE, DOWN_SOUTH, STAIR_SHAPE, OUTER_LEFT),
GBlockstate.when(EDGE, DOWN_EAST, STAIR_SHAPE, OUTER_RIGHT),
GBlockstate.when(EDGE, EAST_SOUTH, STAIR_SHAPE, OUTER_RIGHT)),
GBlockstate.variant(double_outer_id, true, R0, R0))
.with(When.anyOf(
GBlockstate.when(EDGE, DOWN_SOUTH, STAIR_SHAPE, OUTER_RIGHT),
GBlockstate.when(EDGE, WEST_DOWN, STAIR_SHAPE, OUTER_RIGHT),
GBlockstate.when(EDGE, SOUTH_WEST, STAIR_SHAPE, OUTER_RIGHT)),
GBlockstate.variant(double_outer_id, true, R0, R90))
.with(When.anyOf(
GBlockstate.when(EDGE, NORTH_DOWN, STAIR_SHAPE, OUTER_RIGHT),
GBlockstate.when(EDGE, WEST_DOWN, STAIR_SHAPE, OUTER_LEFT),
GBlockstate.when(EDGE, WEST_NORTH, STAIR_SHAPE, OUTER_RIGHT)),
GBlockstate.variant(double_outer_id, true, R0, R180))
.with(When.anyOf(
GBlockstate.when(EDGE, NORTH_DOWN, STAIR_SHAPE, OUTER_LEFT),
GBlockstate.when(EDGE, DOWN_EAST, STAIR_SHAPE, OUTER_LEFT),
GBlockstate.when(EDGE, NORTH_EAST, STAIR_SHAPE, OUTER_RIGHT)),
GBlockstate.variant(double_outer_id, true, R0, R270))
/* OUTER TOP */
.with(When.anyOf(
GBlockstate.when(EDGE, UP_NORTH, STAIR_SHAPE, OUTER_LEFT),
GBlockstate.when(EDGE, EAST_UP, STAIR_SHAPE, OUTER_LEFT),
GBlockstate.when(EDGE, NORTH_EAST, STAIR_SHAPE, OUTER_LEFT)),
GBlockstate.variant(double_outer_id, true, R180, R0))
.with(When.anyOf(
GBlockstate.when(EDGE, SOUTH_UP, STAIR_SHAPE, OUTER_LEFT),
GBlockstate.when(EDGE, EAST_UP, STAIR_SHAPE, OUTER_RIGHT),
GBlockstate.when(EDGE, EAST_SOUTH, STAIR_SHAPE, OUTER_LEFT)),
GBlockstate.variant(double_outer_id, true, R180, R90))
.with(When.anyOf(
GBlockstate.when(EDGE, SOUTH_UP, STAIR_SHAPE, OUTER_RIGHT),
GBlockstate.when(EDGE, UP_WEST, STAIR_SHAPE, OUTER_RIGHT),
GBlockstate.when(EDGE, SOUTH_WEST, STAIR_SHAPE, OUTER_LEFT)),
GBlockstate.variant(double_outer_id, true, R180, R180))
.with(When.anyOf(
GBlockstate.when(EDGE, UP_NORTH, STAIR_SHAPE, OUTER_RIGHT),
GBlockstate.when(EDGE, UP_WEST, STAIR_SHAPE, OUTER_LEFT),
GBlockstate.when(EDGE, WEST_NORTH, STAIR_SHAPE, OUTER_LEFT)),
GBlockstate.variant(double_outer_id, true, R180, R270));
}
@Override
public void setRecipe(RecipeExporter exporter) {
RecipeProvider.offerStonecuttingRecipe(exporter, RecipeCategory.BUILDING_BLOCKS, this, ReFramed.CUBE);
ShapedRecipeJsonBuilder
.create(RecipeCategory.BUILDING_BLOCKS, this, 4)
.pattern("I ")
.pattern("II ")
.pattern("III")
.input('I', ReFramed.CUBE)
.criterion(FabricRecipeProvider.hasItem(ReFramed.CUBE), FabricRecipeProvider.conditionsFromItem(ReFramed.CUBE))
.criterion(FabricRecipeProvider.hasItem(this), FabricRecipeProvider.conditionsFromItem(this))
.offerTo(exporter);
}
static {
final VoxelShape STRAIGHT = VoxelShapes.combineAndSimplify(
createCuboidShape(0, 8, 0, 16, 16, 8),
createCuboidShape(0, 0, 0, 16, 8, 16),
BooleanBiFunction.OR
);
final VoxelShape INNER = Stream.of(
createCuboidShape(0, 8, 0, 16, 16, 8),
createCuboidShape(0, 8, 8, 8, 16, 16),
createCuboidShape(0, 0, 0, 16, 8, 16)
).reduce((v1, v2) -> VoxelShapes.combineAndSimplify(v1, v2, BooleanBiFunction.OR)).get();
final VoxelShape OUTER = Stream.of(
createCuboidShape(0, 8, 0, 8, 16, 8),
createCuboidShape(8, 0, 0, 16, 8, 8),
createCuboidShape(0, 0, 0, 8, 8, 16)
).reduce((v1, v2) -> VoxelShapes.combineAndSimplify(v1, v2, BooleanBiFunction.OR)).get();
final VoxelShape JUNCTION = VoxelShapes.combineAndSimplify(
createCuboidShape(0, 8, 0, 8, 16, 8),
createCuboidShape(0, 0, 0, 16, 8, 16),
BooleanBiFunction.OR
);
STAIR_VOXELS = VoxelListBuilder.create(STRAIGHT, 108)
.add(INNER).add(VoxelHelper::rotateY)
.add(OUTER).add(VoxelHelper::rotateY)
.add(JUNCTION).add(VoxelHelper::rotateY)
.add(JUNCTION, VoxelHelper::rotateX, VoxelHelper::rotateZ).add(VoxelHelper::rotateZ)
// DOWN_SOUTH
.add(0, VoxelHelper::rotateCX)
.add(1, VoxelHelper::rotateCX).add(2, VoxelHelper::rotateCX)
.add(3, VoxelHelper::rotateCX).add(4, VoxelHelper::rotateCX)
.add(5, VoxelHelper::rotateCX).add(6, VoxelHelper::rotateCX)
.add(7, VoxelHelper::rotateCX).add(8, VoxelHelper::rotateCX)
// SOUTH_UP
.add(9, VoxelHelper::rotateCX)
.add(10, VoxelHelper::rotateCX).add(11, VoxelHelper::rotateCX)
.add(12, VoxelHelper::rotateCX).add(13, VoxelHelper::rotateCX)
.add(14, VoxelHelper::rotateCX).add(15, VoxelHelper::rotateCX)
.add(16, VoxelHelper::rotateCX).add(17, VoxelHelper::rotateCX)
// UP_NORTH
.add(18, VoxelHelper::rotateCX)
.add(19, VoxelHelper::rotateCX).add(20, VoxelHelper::rotateCX)
.add(21, VoxelHelper::rotateCX).add(22, VoxelHelper::rotateCX)
.add(23, VoxelHelper::rotateCX).add(24, VoxelHelper::rotateCX)
.add(25, VoxelHelper::rotateCX).add(26, VoxelHelper::rotateCX)
// WEST_DOWN
.add(0, VoxelHelper::rotateCY)
.add(10).add(1)
.add(12).add(3)
.add(16).add(5)
.add(7, VoxelHelper::rotateCY).add(8, VoxelHelper::rotateCY)
// DOWN_EAST
.add(36, VoxelHelper::rotateZ)
.add(11).add(2)
.add(13).add(4)
.add(41, VoxelHelper::rotateZ).add(42, VoxelHelper::rotateZ)
.add(17).add(6)
// EAST_UP
.add(45, VoxelHelper::rotateZ)
.add(20).add(29)
.add(22).add(31)
.add(24).add(35)
.add(52, VoxelHelper::rotateZ).add(53, VoxelHelper::rotateZ)
// UP_WEST
.add(54, VoxelHelper::rotateZ)
.add(19).add(28)
.add(21).add(30)
.add(59, VoxelHelper::rotateZ).add(60, VoxelHelper::rotateZ)
.add(23).add(34)
// WEST_NORTH
.add(0, VoxelHelper::rotateCZ)
.add(1).add(28)
.add(3).add(30)
.add(7).add(32)
.add(44).add(69)
// NORTH_EAST
.add(72, VoxelHelper::rotateY)
.add(2).add(29)
.add(4).add(31)
.add(51).add(62)
.add(8).add(33)
// EAST_SOUTH
.add(81, VoxelHelper::rotateY)
.add(11).add(20)
.add(13).add(22)
.add(15).add(26)
.add(50).add(61)
// SOUTH_WEST
.add(90, VoxelHelper::rotateY)
.add(10).add(19)
.add(12).add(21)
.add(43).add(68)
.add(14).add(25)
.build();
}
}

View File

@@ -1,571 +0,0 @@
package fr.adrien1106.reframed.block;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.generator.GBlockstate;
import fr.adrien1106.reframed.generator.MultipartBlockStateProvider;
import fr.adrien1106.reframed.util.VoxelHelper;
import fr.adrien1106.reframed.util.property.StairDirection;
import fr.adrien1106.reframed.util.property.StairShape;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext;
import net.minecraft.data.client.*;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.state.StateManager;
import net.minecraft.state.property.EnumProperty;
import net.minecraft.util.Identifier;
import net.minecraft.util.function.BooleanBiFunction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Direction.Axis;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
import net.minecraft.world.WorldAccess;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import static fr.adrien1106.reframed.util.property.StairShape.*;
import static net.minecraft.data.client.VariantSettings.Rotation.*;
import static fr.adrien1106.reframed.util.property.StairDirection.*;
public class ReFramedStairsBlock extends WaterloggableReFramedBlock implements MultipartBlockStateProvider {
public static final EnumProperty<StairDirection> FACING = EnumProperty.of("facing", StairDirection.class);
public static final EnumProperty<StairShape> SHAPE = EnumProperty.of("shape", StairShape.class);
private static final List<VoxelShape> VOXEL_LIST = new ArrayList<>(52);
public ReFramedStairsBlock(Settings settings) {
super(settings);
setDefaultState(getDefaultState().with(FACING, StairDirection.NORTH_DOWN).with(SHAPE, STRAIGHT));
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(builder.add(FACING).add(SHAPE));
}
@Override
public BlockState getStateForNeighborUpdate(BlockState state, Direction direction, BlockState neighbor_state, WorldAccess world, BlockPos pos, BlockPos moved) {
return super.getStateForNeighborUpdate(state, direction, neighbor_state, world, pos, moved)
.with(SHAPE, getPlacementShape(state.get(FACING), world, pos));
}
@Nullable
@Override // Pretty happy of how clean it is (also got it on first try :) )
public BlockState getPlacementState(ItemPlacementContext ctx) {
BlockState state = super.getPlacementState(ctx);
Direction side = ctx.getSide().getOpposite();
BlockPos block_pos = ctx.getBlockPos();
Vec3d hit_pos = ctx.getHitPos();
Vec3d pos = new Vec3d(
hit_pos.getX() - block_pos.getX() - .5d,
hit_pos.getY() - block_pos.getY() - .5d,
hit_pos.getZ() - block_pos.getZ() - .5d
);
Stream<Axis> axes = Stream.of(Axis.values()).filter(axis -> !axis.equals(side.getAxis()));
Axis axis = axes.reduce((axis_1, axis_2) ->
Math.abs(axis_1.choose(pos.x, pos.y, pos.z)) > Math.abs(axis_2.choose(pos.x, pos.y, pos.z))
? axis_1
: axis_2
).get();
Direction part_direction = Direction.from(
axis,
axis.choose(pos.x, pos.y, pos.z) > 0
? Direction.AxisDirection.POSITIVE
: Direction.AxisDirection.NEGATIVE
);
StairDirection face = StairDirection.getByDirections(side, part_direction);
return state.with(FACING, face).with(SHAPE, getPlacementShape(face, ctx.getWorld(), block_pos));
}
@Override
public void onStateReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) {
super.onStateReplaced(state, world, pos, newState, moved);
//StairsBlock onStateReplaced is Weird! it doesn't delegate to regular block onStateReplaced!
if(!state.isOf(newState.getBlock())) world.removeBlockEntity(pos);
}
/* ---------------------------------- DON'T GO FURTHER IF YOU LIKE HAVING EYES ---------------------------------- */
@Override
public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
StairShape shape = state.get(SHAPE);
StairDirection direction = state.get(FACING);
return switch (shape) {
case STRAIGHT ->
switch (direction) {
case DOWN_SOUTH -> VOXEL_LIST.get(0);
case NORTH_DOWN -> VOXEL_LIST.get(1);
case UP_NORTH -> VOXEL_LIST.get(2);
case SOUTH_UP -> VOXEL_LIST.get(3);
case DOWN_EAST -> VOXEL_LIST.get(4);
case WEST_DOWN -> VOXEL_LIST.get(5);
case UP_WEST -> VOXEL_LIST.get(6);
case EAST_UP -> VOXEL_LIST.get(7);
case NORTH_EAST -> VOXEL_LIST.get(8);
case EAST_SOUTH -> VOXEL_LIST.get(9);
case SOUTH_WEST -> VOXEL_LIST.get(10);
case WEST_NORTH -> VOXEL_LIST.get(11);
};
case INNER_LEFT ->
switch (direction) {
case WEST_DOWN, NORTH_DOWN -> VOXEL_LIST.get(44);
case DOWN_EAST -> VOXEL_LIST.get(45);
case DOWN_SOUTH -> VOXEL_LIST.get(47);
case UP_WEST, UP_NORTH, WEST_NORTH -> VOXEL_LIST.get(48);
case EAST_UP, NORTH_EAST -> VOXEL_LIST.get(49);
case EAST_SOUTH -> VOXEL_LIST.get(50);
case SOUTH_UP, SOUTH_WEST -> VOXEL_LIST.get(51);
};
case INNER_RIGHT ->
switch (direction) {
case WEST_NORTH -> VOXEL_LIST.get(44);
case NORTH_DOWN, NORTH_EAST -> VOXEL_LIST.get(45);
case DOWN_EAST, DOWN_SOUTH, EAST_SOUTH -> VOXEL_LIST.get(46);
case WEST_DOWN, SOUTH_WEST -> VOXEL_LIST.get(47);
case UP_NORTH -> VOXEL_LIST.get(49);
case EAST_UP, SOUTH_UP -> VOXEL_LIST.get(50);
case UP_WEST -> VOXEL_LIST.get(51);
};
case OUTER_LEFT ->
switch (direction) {
case DOWN_EAST -> VOXEL_LIST.get(43);
case WEST_DOWN, NORTH_DOWN -> VOXEL_LIST.get(42);
case DOWN_SOUTH -> VOXEL_LIST.get(41);
case EAST_UP, NORTH_EAST -> VOXEL_LIST.get(39);
case UP_WEST, UP_NORTH, WEST_NORTH -> VOXEL_LIST.get(38);
case SOUTH_UP, SOUTH_WEST -> VOXEL_LIST.get(37);
case EAST_SOUTH -> VOXEL_LIST.get(36);
};
case OUTER_RIGHT ->
switch (direction) {
case NORTH_DOWN, NORTH_EAST -> VOXEL_LIST.get(43);
case WEST_NORTH -> VOXEL_LIST.get(42);
case WEST_DOWN, SOUTH_WEST -> VOXEL_LIST.get(41);
case DOWN_EAST, DOWN_SOUTH, EAST_SOUTH -> VOXEL_LIST.get(40);
case UP_NORTH -> VOXEL_LIST.get(39);
case UP_WEST -> VOXEL_LIST.get(37);
case EAST_UP, SOUTH_UP -> VOXEL_LIST.get(36);
};
case FIRST_OUTER_LEFT ->
switch (direction) {
case WEST_DOWN, NORTH_DOWN -> VOXEL_LIST.get(14);
case SOUTH_UP -> VOXEL_LIST.get(17);
case EAST_UP -> VOXEL_LIST.get(19);
case EAST_SOUTH -> VOXEL_LIST.get(20);
case DOWN_SOUTH -> VOXEL_LIST.get(22);
case UP_NORTH, WEST_NORTH -> VOXEL_LIST.get(25);
case SOUTH_WEST -> VOXEL_LIST.get(28);
case UP_WEST -> VOXEL_LIST.get(31);
case DOWN_EAST -> VOXEL_LIST.get(34);
case NORTH_EAST -> VOXEL_LIST.get(35);
};
case FIRST_OUTER_RIGHT ->
switch (direction) {
case NORTH_DOWN -> VOXEL_LIST.get(15);
case SOUTH_UP, EAST_UP -> VOXEL_LIST.get(16);
case WEST_DOWN -> VOXEL_LIST.get(13);
case DOWN_SOUTH, EAST_SOUTH -> VOXEL_LIST.get(23);
case UP_NORTH -> VOXEL_LIST.get(24);
case WEST_NORTH -> VOXEL_LIST.get(26);
case UP_WEST -> VOXEL_LIST.get(28);
case SOUTH_WEST -> VOXEL_LIST.get(29);
case DOWN_EAST -> VOXEL_LIST.get(33);
case NORTH_EAST -> VOXEL_LIST.get(34);
};
case SECOND_OUTER_LEFT ->
switch (direction) {
case DOWN_EAST -> VOXEL_LIST.get(15);
case DOWN_SOUTH -> VOXEL_LIST.get(13);
case UP_WEST, UP_NORTH -> VOXEL_LIST.get(18);
case SOUTH_UP, SOUTH_WEST -> VOXEL_LIST.get(21);
case NORTH_EAST -> VOXEL_LIST.get(24);
case NORTH_DOWN -> VOXEL_LIST.get(26);
case WEST_DOWN -> VOXEL_LIST.get(30);
case WEST_NORTH -> VOXEL_LIST.get(31);
case EAST_SOUTH -> VOXEL_LIST.get(32);
case EAST_UP -> VOXEL_LIST.get(35);
};
case SECOND_OUTER_RIGHT ->
switch (direction) {
case DOWN_SOUTH, DOWN_EAST -> VOXEL_LIST.get(12);
case UP_WEST -> VOXEL_LIST.get(17);
case UP_NORTH -> VOXEL_LIST.get(19);
case SOUTH_UP -> VOXEL_LIST.get(20);
case SOUTH_WEST -> VOXEL_LIST.get(22);
case NORTH_EAST, NORTH_DOWN -> VOXEL_LIST.get(27);
case WEST_DOWN -> VOXEL_LIST.get(29);
case WEST_NORTH -> VOXEL_LIST.get(30);
case EAST_UP -> VOXEL_LIST.get(32);
case EAST_SOUTH -> VOXEL_LIST.get(33);
};
};
}
private static String getNeighborPos(StairDirection face, Direction direction, Boolean reverse, Direction reference, BlockView world, BlockPos pos) {
BlockState block_state = world.getBlockState(
pos.offset(reverse ? direction.getOpposite() : direction)
);
if (block_state.getBlock() instanceof ReFramedStairsBlock && block_state.get(FACING).hasDirection(reference)) {
if (block_state.get(FACING).hasDirection(face.getLeftDirection())) return "left";
else if (block_state.get(FACING).hasDirection(face.getRightDirection())) return "right";
}
return "";
}
private static StairShape getPlacementShape(StairDirection face, BlockView world, BlockPos pos) {
StairShape shape = STRAIGHT;
String sol = getNeighborPos(face, face.getFirstDirection(), true, face.getSecondDirection(), world, pos);
switch (sol) {
case "right": return INNER_RIGHT;
case "left": return INNER_LEFT;
}
sol = getNeighborPos(face, face.getSecondDirection(), true, face.getFirstDirection(), world, pos);
switch (sol) {
case "right": return INNER_RIGHT;
case "left": return INNER_LEFT;
}
sol = getNeighborPos(face, face.getFirstDirection(), false, face.getSecondDirection(), world, pos);
switch (sol) {
case "right" -> shape = FIRST_OUTER_RIGHT;
case "left" -> shape = FIRST_OUTER_LEFT;
}
sol = getNeighborPos(face, face.getSecondDirection(), false, face.getFirstDirection(), world, pos);
switch (sol) {
case "right" -> {
if (shape.equals(STRAIGHT)) shape = SECOND_OUTER_RIGHT;
else if (shape.equals(FIRST_OUTER_RIGHT)) shape = OUTER_RIGHT;
}
case "left" -> {
if (shape.equals(STRAIGHT)) shape = SECOND_OUTER_LEFT;
else if (shape.equals(FIRST_OUTER_LEFT)) shape = OUTER_LEFT;
}
}
return shape;
}
@Override
public MultipartBlockStateSupplier getMultipart() {
Identifier straight_id = ReFramed.id("stairs_special");
Identifier double_outer_id = ReFramed.id("double_outer_stairs_special");
Identifier inner_id = ReFramed.id("inner_stairs_special");
Identifier outer_id = ReFramed.id("outer_stairs_special");
Identifier outer_side_id = ReFramed.id("outer_side_stairs_special");
return MultipartBlockStateSupplier.create(this)
/* STRAIGHT X AXIS */
.with(GBlockstate.when(FACING, DOWN_EAST, SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R0, R0))
.with(GBlockstate.when(FACING, EAST_UP, SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R180, R0))
.with(GBlockstate.when(FACING, UP_WEST, SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R180, R180))
.with(GBlockstate.when(FACING, WEST_DOWN, SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R0, R180))
/* STRAIGHT Y AXIS */
.with(GBlockstate.when(FACING, EAST_SOUTH, SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R90, R0))
.with(GBlockstate.when(FACING, SOUTH_WEST, SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R90, R90))
.with(GBlockstate.when(FACING, WEST_NORTH, SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R90, R180))
.with(GBlockstate.when(FACING, NORTH_EAST, SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R90, R270))
/* STRAIGHT Z AXIS */
.with(GBlockstate.when(FACING, DOWN_SOUTH, SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R0, R90))
.with(GBlockstate.when(FACING, NORTH_DOWN, SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R0, R270))
.with(GBlockstate.when(FACING, UP_NORTH, SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R180, R270))
.with(GBlockstate.when(FACING, SOUTH_UP, SHAPE, STRAIGHT),
GBlockstate.variant(straight_id, true, R180, R90))
/* INNER BOTTOM */
.with(When.anyOf(
GBlockstate.when(FACING, NORTH_DOWN, SHAPE, INNER_LEFT),
GBlockstate.when(FACING, WEST_NORTH, SHAPE, INNER_RIGHT),
GBlockstate.when(FACING, WEST_DOWN, SHAPE, INNER_LEFT)),
GBlockstate.variant(inner_id, true, R0, R180))
.with(When.anyOf(
GBlockstate.when(FACING, NORTH_DOWN, SHAPE, INNER_RIGHT),
GBlockstate.when(FACING, NORTH_EAST, SHAPE, INNER_RIGHT),
GBlockstate.when(FACING, DOWN_EAST, SHAPE, INNER_LEFT)),
GBlockstate.variant(inner_id, true, R0, R270))
.with(When.anyOf(
GBlockstate.when(FACING, DOWN_SOUTH, SHAPE, INNER_RIGHT),
GBlockstate.when(FACING, EAST_SOUTH, SHAPE, INNER_RIGHT),
GBlockstate.when(FACING, DOWN_EAST, SHAPE, INNER_RIGHT)),
GBlockstate.variant(inner_id, true, R0, R0))
.with(When.anyOf(
GBlockstate.when(FACING, DOWN_SOUTH, SHAPE, INNER_LEFT),
GBlockstate.when(FACING, SOUTH_WEST, SHAPE, INNER_RIGHT),
GBlockstate.when(FACING, WEST_DOWN, SHAPE, INNER_RIGHT)),
GBlockstate.variant(inner_id, true, R0, R90))
/* INNER TOP */
.with(When.anyOf(
GBlockstate.when(FACING, EAST_UP, SHAPE, INNER_LEFT),
GBlockstate.when(FACING, NORTH_EAST, SHAPE, INNER_LEFT),
GBlockstate.when(FACING, UP_NORTH, SHAPE, INNER_RIGHT)),
GBlockstate.variant(inner_id, true, R180, R0))
.with(When.anyOf(
GBlockstate.when(FACING, EAST_UP, SHAPE, INNER_RIGHT),
GBlockstate.when(FACING, EAST_SOUTH, SHAPE, INNER_LEFT),
GBlockstate.when(FACING, SOUTH_UP, SHAPE, INNER_RIGHT)),
GBlockstate.variant(inner_id, true, R180, R90))
.with(When.anyOf(
GBlockstate.when(FACING, SOUTH_UP, SHAPE, INNER_LEFT),
GBlockstate.when(FACING, SOUTH_WEST, SHAPE, INNER_LEFT),
GBlockstate.when(FACING, UP_WEST, SHAPE, INNER_RIGHT)),
GBlockstate.variant(inner_id, true, R180, R180))
.with(When.anyOf(
GBlockstate.when(FACING, UP_NORTH, SHAPE, INNER_LEFT),
GBlockstate.when(FACING, WEST_NORTH, SHAPE, INNER_LEFT),
GBlockstate.when(FACING, UP_WEST, SHAPE, INNER_LEFT)),
GBlockstate.variant(inner_id, true, R180, R270))
/* OUTER BOTTOM */
.with(When.anyOf(
GBlockstate.when(FACING, DOWN_SOUTH, SHAPE, SECOND_OUTER_RIGHT),
GBlockstate.when(FACING, DOWN_EAST, SHAPE, SECOND_OUTER_RIGHT)),
GBlockstate.variant(outer_id, true, R0, R0))
.with(When.anyOf(
GBlockstate.when(FACING, DOWN_SOUTH, SHAPE, SECOND_OUTER_LEFT),
GBlockstate.when(FACING, WEST_DOWN, SHAPE, FIRST_OUTER_RIGHT)),
GBlockstate.variant(outer_id, true, R0, R90))
.with(When.anyOf(
GBlockstate.when(FACING, NORTH_DOWN, SHAPE, FIRST_OUTER_LEFT),
GBlockstate.when(FACING, WEST_DOWN, SHAPE, FIRST_OUTER_LEFT)),
GBlockstate.variant(outer_id, true, R0, R180))
.with(When.anyOf(
GBlockstate.when(FACING, NORTH_DOWN, SHAPE, FIRST_OUTER_RIGHT),
GBlockstate.when(FACING, DOWN_EAST, SHAPE, SECOND_OUTER_LEFT)),
GBlockstate.variant(outer_id, true, R0, R270))
/* OUTER TOP */
.with(When.anyOf(
GBlockstate.when(FACING, UP_NORTH, SHAPE, SECOND_OUTER_RIGHT),
GBlockstate.when(FACING, EAST_UP, SHAPE, FIRST_OUTER_LEFT)),
GBlockstate.variant(outer_id, true, R180, R0))
.with(When.anyOf(
GBlockstate.when(FACING, SOUTH_UP, SHAPE, FIRST_OUTER_RIGHT),
GBlockstate.when(FACING, EAST_UP, SHAPE, FIRST_OUTER_RIGHT)),
GBlockstate.variant(outer_id, true, R180, R90))
.with(When.anyOf(
GBlockstate.when(FACING, SOUTH_UP, SHAPE, FIRST_OUTER_LEFT),
GBlockstate.when(FACING, UP_WEST, SHAPE, SECOND_OUTER_RIGHT)),
GBlockstate.variant(outer_id, true, R180, R180))
.with(When.anyOf(
GBlockstate.when(FACING, UP_NORTH, SHAPE, SECOND_OUTER_LEFT),
GBlockstate.when(FACING, UP_WEST, SHAPE, SECOND_OUTER_LEFT)),
GBlockstate.variant(outer_id, true, R180, R270))
/* OUTER EAST */
.with(When.anyOf(
GBlockstate.when(FACING, EAST_SOUTH, SHAPE, SECOND_OUTER_RIGHT),
GBlockstate.when(FACING, DOWN_EAST, SHAPE, FIRST_OUTER_RIGHT)),
GBlockstate.variant(outer_side_id, true, R0, R0))
.with(When.anyOf(
GBlockstate.when(FACING, EAST_SOUTH, SHAPE, SECOND_OUTER_LEFT),
GBlockstate.when(FACING, EAST_UP, SHAPE, SECOND_OUTER_RIGHT)),
GBlockstate.variant(outer_side_id, true, R90, R0))
.with(When.anyOf(
GBlockstate.when(FACING, NORTH_EAST, SHAPE, FIRST_OUTER_LEFT),
GBlockstate.when(FACING, EAST_UP, SHAPE, SECOND_OUTER_LEFT)),
GBlockstate.variant(outer_side_id, true, R180, R0))
.with(When.anyOf(
GBlockstate.when(FACING, NORTH_EAST, SHAPE, FIRST_OUTER_RIGHT),
GBlockstate.when(FACING, DOWN_EAST, SHAPE, FIRST_OUTER_LEFT)),
GBlockstate.variant(outer_side_id, true, R270, R0))
/* OUTER SOUTH */
.with(When.anyOf(
GBlockstate.when(FACING, SOUTH_WEST, SHAPE, SECOND_OUTER_RIGHT),
GBlockstate.when(FACING, DOWN_SOUTH, SHAPE, FIRST_OUTER_LEFT)),
GBlockstate.variant(outer_side_id, true, R0, R90))
.with(When.anyOf(
GBlockstate.when(FACING, SOUTH_WEST, SHAPE, SECOND_OUTER_LEFT),
GBlockstate.when(FACING, SOUTH_UP, SHAPE, SECOND_OUTER_LEFT)),
GBlockstate.variant(outer_side_id, true, R90, R90))
.with(When.anyOf(
GBlockstate.when(FACING, EAST_SOUTH, SHAPE, FIRST_OUTER_LEFT),
GBlockstate.when(FACING, SOUTH_UP, SHAPE, SECOND_OUTER_RIGHT)),
GBlockstate.variant(outer_side_id, true, R180, R90))
.with(When.anyOf(
GBlockstate.when(FACING, EAST_SOUTH, SHAPE, FIRST_OUTER_RIGHT),
GBlockstate.when(FACING, DOWN_SOUTH, SHAPE, FIRST_OUTER_RIGHT)),
GBlockstate.variant(outer_side_id, true, R270, R90))
/* OUTER WEST */
.with(When.anyOf(
GBlockstate.when(FACING, WEST_NORTH, SHAPE, SECOND_OUTER_RIGHT),
GBlockstate.when(FACING, WEST_DOWN, SHAPE, SECOND_OUTER_LEFT)),
GBlockstate.variant(outer_side_id, true, R0, R180))
.with(When.anyOf(
GBlockstate.when(FACING, WEST_NORTH, SHAPE, SECOND_OUTER_LEFT),
GBlockstate.when(FACING, UP_WEST, SHAPE, FIRST_OUTER_LEFT)),
GBlockstate.variant(outer_side_id, true, R90, R180))
.with(When.anyOf(
GBlockstate.when(FACING, SOUTH_WEST, SHAPE, FIRST_OUTER_LEFT),
GBlockstate.when(FACING, UP_WEST, SHAPE, FIRST_OUTER_RIGHT)),
GBlockstate.variant(outer_side_id, true, R180, R180))
.with(When.anyOf(
GBlockstate.when(FACING, SOUTH_WEST, SHAPE, FIRST_OUTER_RIGHT),
GBlockstate.when(FACING, WEST_DOWN, SHAPE, SECOND_OUTER_RIGHT)),
GBlockstate.variant(outer_side_id, true, R270, R180))
/* OUTER NORTH */
.with(When.anyOf(
GBlockstate.when(FACING, NORTH_EAST, SHAPE, SECOND_OUTER_RIGHT),
GBlockstate.when(FACING, NORTH_DOWN, SHAPE, SECOND_OUTER_RIGHT)),
GBlockstate.variant(outer_side_id, true, R0, R270))
.with(When.anyOf(
GBlockstate.when(FACING, NORTH_EAST, SHAPE, SECOND_OUTER_LEFT),
GBlockstate.when(FACING, UP_NORTH, SHAPE, FIRST_OUTER_RIGHT)),
GBlockstate.variant(outer_side_id, true, R90, R270))
.with(When.anyOf(
GBlockstate.when(FACING, WEST_NORTH, SHAPE, FIRST_OUTER_LEFT),
GBlockstate.when(FACING, UP_NORTH, SHAPE, FIRST_OUTER_LEFT)),
GBlockstate.variant(outer_side_id, true, R180, R270))
.with(When.anyOf(
GBlockstate.when(FACING, WEST_NORTH, SHAPE, FIRST_OUTER_RIGHT),
GBlockstate.when(FACING, NORTH_DOWN, SHAPE, SECOND_OUTER_LEFT)),
GBlockstate.variant(outer_side_id, true, R270, R270))
/* OUTER BOTTOM */
.with(When.anyOf(
GBlockstate.when(FACING, DOWN_SOUTH, SHAPE, OUTER_RIGHT),
GBlockstate.when(FACING, DOWN_EAST, SHAPE, OUTER_RIGHT),
GBlockstate.when(FACING, EAST_SOUTH, SHAPE, OUTER_RIGHT)),
GBlockstate.variant(double_outer_id, true, R0, R0))
.with(When.anyOf(
GBlockstate.when(FACING, DOWN_SOUTH, SHAPE, OUTER_LEFT),
GBlockstate.when(FACING, WEST_DOWN, SHAPE, OUTER_RIGHT),
GBlockstate.when(FACING, SOUTH_WEST, SHAPE, OUTER_RIGHT)),
GBlockstate.variant(double_outer_id, true, R0, R90))
.with(When.anyOf(
GBlockstate.when(FACING, NORTH_DOWN, SHAPE, OUTER_LEFT),
GBlockstate.when(FACING, WEST_DOWN, SHAPE, OUTER_LEFT),
GBlockstate.when(FACING, WEST_NORTH, SHAPE, OUTER_RIGHT)),
GBlockstate.variant(double_outer_id, true, R0, R180))
.with(When.anyOf(
GBlockstate.when(FACING, NORTH_DOWN, SHAPE, OUTER_RIGHT),
GBlockstate.when(FACING, DOWN_EAST, SHAPE, OUTER_LEFT),
GBlockstate.when(FACING, NORTH_EAST, SHAPE, OUTER_RIGHT)),
GBlockstate.variant(double_outer_id, true, R0, R270))
/* OUTER BOTTOM */
.with(When.anyOf(
GBlockstate.when(FACING, UP_NORTH, SHAPE, OUTER_RIGHT),
GBlockstate.when(FACING, EAST_UP, SHAPE, OUTER_LEFT),
GBlockstate.when(FACING, NORTH_EAST, SHAPE, OUTER_LEFT)),
GBlockstate.variant(double_outer_id, true, R180, R0))
.with(When.anyOf(
GBlockstate.when(FACING, SOUTH_UP, SHAPE, OUTER_RIGHT),
GBlockstate.when(FACING, EAST_UP, SHAPE, OUTER_RIGHT),
GBlockstate.when(FACING, EAST_SOUTH, SHAPE, OUTER_LEFT)),
GBlockstate.variant(double_outer_id, true, R180, R90))
.with(When.anyOf(
GBlockstate.when(FACING, SOUTH_UP, SHAPE, OUTER_LEFT),
GBlockstate.when(FACING, UP_WEST, SHAPE, OUTER_RIGHT),
GBlockstate.when(FACING, SOUTH_WEST, SHAPE, OUTER_LEFT)),
GBlockstate.variant(double_outer_id, true, R180, R90))
.with(When.anyOf(
GBlockstate.when(FACING, UP_NORTH, SHAPE, OUTER_LEFT),
GBlockstate.when(FACING, UP_WEST, SHAPE, OUTER_LEFT),
GBlockstate.when(FACING, WEST_NORTH, SHAPE, OUTER_LEFT)),
GBlockstate.variant(double_outer_id, true, R270, R90));
}
static {
final VoxelShape STRAIGHT = Stream.of(
VoxelShapes.cuboid(0.0f, 0.0f, 0.0f, 1.0f, 0.5f, 1.0f),
VoxelShapes.cuboid(0.0f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f)
).reduce((previous, current) -> VoxelShapes.combineAndSimplify(previous, current, BooleanBiFunction.OR)).get();
final VoxelShape JUNCTION = Stream.of(
VoxelShapes.cuboid(0.0f, 0.0f, 0.0f, 1.0f, 0.5f, 1.0f),
VoxelShapes.cuboid(0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f)
).reduce((previous, current) -> VoxelShapes.combineAndSimplify(previous, current, BooleanBiFunction.OR)).get();
final VoxelShape OUTER = Stream.of(
VoxelShapes.cuboid(0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f),
VoxelShapes.cuboid(0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.5f),
VoxelShapes.cuboid(0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 1.0f),
VoxelShapes.cuboid(0.5f, 0.0f, 0.5f, 1.0f, 0.5f, 1.0f)
).reduce((previous, current) -> VoxelShapes.combineAndSimplify(previous, current, BooleanBiFunction.OR)).get();
final VoxelShape INNER = Stream.of(
VoxelShapes.cuboid(0.0f, 0.0f, 0.0f, 1.0f, 0.5f, 1.0f),
VoxelShapes.cuboid(0.0f, 0.5f, 0.0f, 0.5f, 1.0f, 1.0f),
VoxelShapes.cuboid(0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.5f)
).reduce((previous, current) -> VoxelShapes.combineAndSimplify(previous, current, BooleanBiFunction.OR)).get();
VOXEL_LIST.add(STRAIGHT);
VOXEL_LIST.add(VoxelHelper.mirror(STRAIGHT, Axis.Z));
VOXEL_LIST.add(VoxelHelper.mirror(VoxelHelper.rotateCounterClockwise(STRAIGHT, Axis.X), Axis.Z));
VOXEL_LIST.add(VoxelHelper.rotateCounterClockwise(STRAIGHT, Axis.X));
VOXEL_LIST.add(VoxelHelper.rotateCounterClockwise(STRAIGHT, Axis.Y));
VOXEL_LIST.add(VoxelHelper.mirror(VoxelHelper.rotateCounterClockwise(STRAIGHT, Axis.Y), Axis.X));
VOXEL_LIST.add(VoxelHelper.mirror(VoxelHelper.rotateClockwise(VoxelHelper.rotateCounterClockwise(STRAIGHT, Axis.Y), Axis.Z), Axis.X));
VOXEL_LIST.add(VoxelHelper.rotateClockwise(VoxelHelper.rotateCounterClockwise(STRAIGHT, Axis.Y), Axis.Z));
VOXEL_LIST.add(VoxelHelper.rotateCounterClockwise(VoxelHelper.rotateClockwise(STRAIGHT, Axis.Z), Axis.Y));
VOXEL_LIST.add(VoxelHelper.rotateClockwise(STRAIGHT, Axis.Z));
VOXEL_LIST.add(VoxelHelper.rotateClockwise(VoxelHelper.rotateClockwise(STRAIGHT, Axis.Z), Axis.Y));
VOXEL_LIST.add(VoxelHelper.mirror(VoxelHelper.rotateClockwise(VoxelHelper.rotateClockwise(STRAIGHT, Axis.Z), Axis.Y), Axis.Z));
VOXEL_LIST.add(JUNCTION);
VOXEL_LIST.add(VoxelHelper.rotateClockwise(JUNCTION, Axis.Y));
VOXEL_LIST.add(VoxelHelper.rotateClockwise(VoxelHelper.rotateClockwise(JUNCTION, Axis.Y), Axis.Y));
VOXEL_LIST.add(VoxelHelper.rotateCounterClockwise(JUNCTION, Axis.Y));
VOXEL_LIST.add(VoxelHelper.mirror(JUNCTION, Axis.Y));
VOXEL_LIST.add(VoxelHelper.rotateClockwise(VoxelHelper.mirror(JUNCTION, Axis.Y), Axis.Y));
VOXEL_LIST.add(VoxelHelper.rotateClockwise(VoxelHelper.rotateClockwise(VoxelHelper.mirror(JUNCTION, Axis.Y), Axis.Y), Axis.Y));
VOXEL_LIST.add(VoxelHelper.rotateCounterClockwise(VoxelHelper.mirror(JUNCTION, Axis.Y), Axis.Y));
VOXEL_LIST.add(VoxelHelper.rotateCounterClockwise(JUNCTION, Axis.X));
VOXEL_LIST.add(VoxelHelper.rotateClockwise(VoxelHelper.rotateCounterClockwise(JUNCTION, Axis.X), Axis.Z));
VOXEL_LIST.add(VoxelHelper.rotateClockwise(VoxelHelper.rotateClockwise(VoxelHelper.rotateCounterClockwise(JUNCTION, Axis.X), Axis.Z), Axis.Z));
VOXEL_LIST.add(VoxelHelper.rotateCounterClockwise(VoxelHelper.rotateCounterClockwise(JUNCTION, Axis.X), Axis.Z));
VOXEL_LIST.add(VoxelHelper.mirror(VoxelHelper.rotateCounterClockwise(JUNCTION, Axis.X), Axis.Z));
VOXEL_LIST.add(VoxelHelper.rotateClockwise(VoxelHelper.mirror(VoxelHelper.rotateCounterClockwise(JUNCTION, Axis.X), Axis.Z), Axis.Z));
VOXEL_LIST.add(VoxelHelper.rotateClockwise(VoxelHelper.rotateClockwise(VoxelHelper.mirror(VoxelHelper.rotateCounterClockwise(JUNCTION, Axis.X), Axis.Z), Axis.Z), Axis.Z));
VOXEL_LIST.add(VoxelHelper.rotateCounterClockwise(VoxelHelper.mirror(VoxelHelper.rotateCounterClockwise(JUNCTION, Axis.X), Axis.Z), Axis.Z));
VOXEL_LIST.add(VoxelHelper.rotateClockwise(VoxelHelper.rotateCounterClockwise(JUNCTION, Axis.X), Axis.Y));
VOXEL_LIST.add(VoxelHelper.rotateClockwise(VoxelHelper.rotateClockwise(VoxelHelper.rotateCounterClockwise(JUNCTION, Axis.X), Axis.Y), Axis.X));
VOXEL_LIST.add(VoxelHelper.rotateClockwise(VoxelHelper.rotateClockwise(VoxelHelper.rotateClockwise(VoxelHelper.rotateCounterClockwise(JUNCTION, Axis.X), Axis.Y), Axis.X), Axis.X));
VOXEL_LIST.add(VoxelHelper.rotateCounterClockwise(VoxelHelper.rotateClockwise(VoxelHelper.rotateCounterClockwise(JUNCTION, Axis.X), Axis.Y), Axis.X));
VOXEL_LIST.add(VoxelHelper.mirror(VoxelHelper.rotateClockwise(VoxelHelper.rotateCounterClockwise(JUNCTION, Axis.X), Axis.Y), Axis.X));
VOXEL_LIST.add(VoxelHelper.rotateClockwise(VoxelHelper.mirror(VoxelHelper.rotateClockwise(VoxelHelper.rotateCounterClockwise(JUNCTION, Axis.X), Axis.Y), Axis.X), Axis.X));
VOXEL_LIST.add(VoxelHelper.rotateClockwise(VoxelHelper.rotateClockwise(VoxelHelper.mirror(VoxelHelper.rotateClockwise(VoxelHelper.rotateCounterClockwise(JUNCTION, Axis.X), Axis.Y), Axis.X), Axis.X), Axis.X));
VOXEL_LIST.add(VoxelHelper.rotateCounterClockwise(VoxelHelper.mirror(VoxelHelper.rotateClockwise(VoxelHelper.rotateCounterClockwise(JUNCTION, Axis.X), Axis.Y), Axis.X), Axis.X));
VOXEL_LIST.add(OUTER);
VOXEL_LIST.add(VoxelHelper.rotateClockwise(OUTER, Axis.Y));
VOXEL_LIST.add(VoxelHelper.rotateCounterClockwise(VoxelHelper.rotateCounterClockwise(OUTER, Axis.Y), Axis.Y));
VOXEL_LIST.add(VoxelHelper.rotateCounterClockwise(OUTER, Axis.Y));
VOXEL_LIST.add(VoxelHelper.mirror(OUTER, Axis.Y));
VOXEL_LIST.add(VoxelHelper.rotateClockwise(VoxelHelper.mirror(OUTER, Axis.Y), Axis.Y));
VOXEL_LIST.add(VoxelHelper.rotateCounterClockwise(VoxelHelper.rotateCounterClockwise(VoxelHelper.mirror(OUTER, Axis.Y), Axis.Y), Axis.Y));
VOXEL_LIST.add(VoxelHelper.rotateCounterClockwise(VoxelHelper.mirror(OUTER, Axis.Y), Axis.Y));
VOXEL_LIST.add(INNER);
VOXEL_LIST.add(VoxelHelper.rotateClockwise(INNER, Axis.Y));
VOXEL_LIST.add(VoxelHelper.rotateCounterClockwise(VoxelHelper.rotateCounterClockwise(INNER, Axis.Y), Axis.Y));
VOXEL_LIST.add(VoxelHelper.rotateCounterClockwise(INNER, Axis.Y));
VOXEL_LIST.add(VoxelHelper.mirror(INNER, Axis.Y));
VOXEL_LIST.add(VoxelHelper.rotateClockwise(VoxelHelper.mirror(INNER, Axis.Y), Axis.Y));
VOXEL_LIST.add(VoxelHelper.rotateCounterClockwise(VoxelHelper.rotateCounterClockwise(VoxelHelper.mirror(INNER, Axis.Y), Axis.Y), Axis.Y));
VOXEL_LIST.add(VoxelHelper.rotateCounterClockwise(VoxelHelper.mirror(INNER, Axis.Y), Axis.Y));
}
}

View File

@@ -0,0 +1,100 @@
package fr.adrien1106.reframed.block;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.generator.BlockStateProvider;
import fr.adrien1106.reframed.util.blocks.BlockHelper;
import fr.adrien1106.reframed.util.blocks.Edge;
import fr.adrien1106.reframed.util.blocks.StairShape;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.data.client.MultipartBlockStateSupplier;
import net.minecraft.data.server.recipe.RecipeExporter;
import net.minecraft.data.server.recipe.RecipeProvider;
import net.minecraft.data.server.recipe.ShapelessRecipeJsonBuilder;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.recipe.book.RecipeCategory;
import net.minecraft.state.StateManager;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.World;
import net.minecraft.world.WorldAccess;
import org.jetbrains.annotations.Nullable;
import static fr.adrien1106.reframed.block.ReFramedStairBlock.*;
import static fr.adrien1106.reframed.util.VoxelHelper.VoxelListBuilder;
import static fr.adrien1106.reframed.util.blocks.BlockProperties.EDGE;
import static fr.adrien1106.reframed.util.blocks.BlockProperties.STAIR_SHAPE;
public class ReFramedStairsCubeBlock extends ReFramedDoubleBlock implements BlockStateProvider {
private static final VoxelShape[] STAIRS_CUBE_VOXELS = VoxelListBuilder.buildFrom(STAIR_VOXELS);
private record ModelCacheKey(Edge edge, StairShape shape) {}
public ReFramedStairsCubeBlock(Settings settings) {
super(settings);
setDefaultState(getDefaultState().with(EDGE, Edge.NORTH_DOWN).with(STAIR_SHAPE, StairShape.STRAIGHT));
}
@Override
public Object getModelCacheKey(BlockState state) {
return new ModelCacheKey(state.get(EDGE), state.get(STAIR_SHAPE));
}
@Override
public int getModelStateCount() {
return 108; // Has 12 * 9 state combination and 52 models still reduces cache size
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(builder.add(EDGE, STAIR_SHAPE));
}
@Override
public BlockState getStateForNeighborUpdate(BlockState state, Direction direction, BlockState neighbor_state, WorldAccess world, BlockPos pos, BlockPos moved) {
return super.getStateForNeighborUpdate(state, direction, neighbor_state, world, pos, moved)
.with(STAIR_SHAPE, BlockHelper.getStairsShape(state.get(EDGE), world, pos));
}
@Nullable
@Override
public BlockState getPlacementState(ItemPlacementContext ctx) {
Edge face = BlockHelper.getPlacementEdge(ctx);
StairShape shape = BlockHelper.getStairsShape(face, ctx.getWorld(), ctx.getBlockPos());
return super.getPlacementState(ctx).with(EDGE, face).with(STAIR_SHAPE, shape);
}
@Override
public void onStateReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) {
super.onStateReplaced(state, world, pos, newState, moved);
if(!state.isOf(newState.getBlock())) world.removeBlockEntity(pos);
}
@Override
public VoxelShape getShape(BlockState state, int i) {
Edge edge = state.get(EDGE);
StairShape shape = state.get(STAIR_SHAPE);
return i == 2 ? STAIRS_CUBE_VOXELS[edge.getID() * 9 + shape.getID()] : getStairShape(edge, shape);
}
@Override
public MultipartBlockStateSupplier getMultipart() {
return getStairMultipart(this, true);
}
@Override
public void setRecipe(RecipeExporter exporter) {
RecipeProvider.offerStonecuttingRecipe(exporter, RecipeCategory.BUILDING_BLOCKS, this, ReFramed.CUBE);
ShapelessRecipeJsonBuilder
.create(RecipeCategory.BUILDING_BLOCKS, this)
.input(ReFramed.STAIR)
.input(ReFramed.STEP)
.criterion(FabricRecipeProvider.hasItem(ReFramed.CUBE), FabricRecipeProvider.conditionsFromItem(ReFramed.CUBE))
.criterion(FabricRecipeProvider.hasItem(this), FabricRecipeProvider.conditionsFromItem(this))
.offerTo(exporter);
}
}

View File

@@ -0,0 +1,135 @@
package fr.adrien1106.reframed.block;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.generator.BlockStateProvider;
import fr.adrien1106.reframed.generator.GBlockstate;
import fr.adrien1106.reframed.util.VoxelHelper;
import fr.adrien1106.reframed.util.blocks.BlockHelper;
import fr.adrien1106.reframed.util.blocks.Edge;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext;
import net.minecraft.data.client.BlockStateSupplier;
import net.minecraft.data.client.MultipartBlockStateSupplier;
import net.minecraft.data.server.recipe.RecipeExporter;
import net.minecraft.data.server.recipe.RecipeProvider;
import net.minecraft.data.server.recipe.ShapedRecipeJsonBuilder;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.recipe.book.RecipeCategory;
import net.minecraft.state.StateManager;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.BlockView;
import org.jetbrains.annotations.Nullable;
import static fr.adrien1106.reframed.util.VoxelHelper.VoxelListBuilder;
import static fr.adrien1106.reframed.util.blocks.BlockProperties.EDGE;
import static fr.adrien1106.reframed.util.blocks.Edge.*;
import static net.minecraft.data.client.VariantSettings.Rotation.*;
public class ReFramedStepBlock extends WaterloggableReFramedBlock implements BlockStateProvider {
public static final VoxelShape[] STEP_VOXELS;
public ReFramedStepBlock(Settings settings) {
super(settings);
setDefaultState(getDefaultState().with(EDGE, Edge.NORTH_DOWN));
}
@Override
public Object getModelCacheKey(BlockState state) {
return state.get(EDGE);
}
@Override
public int getModelStateCount() {
return 12;
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(builder.add(EDGE));
}
@Nullable
@Override
public BlockState getPlacementState(ItemPlacementContext ctx) {
return super.getPlacementState(ctx).with(EDGE, BlockHelper.getPlacementEdge(ctx));
}
@Override
public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return getStepShape(state.get(EDGE));
}
public static VoxelShape getStepShape(Edge edge) {
return STEP_VOXELS[edge.getID()];
}
@Override
public BlockStateSupplier getMultipart() {
Identifier model_id = ReFramed.id("step_special");
return MultipartBlockStateSupplier.create(this)
/* X AXIS */
.with(GBlockstate.when(EDGE, DOWN_EAST),
GBlockstate.variant(model_id, true, R0, R0))
.with(GBlockstate.when(EDGE, EAST_UP),
GBlockstate.variant(model_id, true, R180, R0))
.with(GBlockstate.when(EDGE, UP_WEST),
GBlockstate.variant(model_id, true, R180, R180))
.with(GBlockstate.when(EDGE, WEST_DOWN),
GBlockstate.variant(model_id, true, R0, R180))
/* Y AXIS */
.with(GBlockstate.when(EDGE, EAST_SOUTH),
GBlockstate.variant(model_id, true, R90, R0))
.with(GBlockstate.when(EDGE, SOUTH_WEST),
GBlockstate.variant(model_id, true, R90, R90))
.with(GBlockstate.when(EDGE, WEST_NORTH),
GBlockstate.variant(model_id, true, R90, R180))
.with(GBlockstate.when(EDGE, NORTH_EAST),
GBlockstate.variant(model_id, true, R90, R270))
/* Z AXIS */
.with(GBlockstate.when(EDGE, DOWN_SOUTH),
GBlockstate.variant(model_id, true, R0, R90))
.with(GBlockstate.when(EDGE, NORTH_DOWN),
GBlockstate.variant(model_id, true, R0, R270))
.with(GBlockstate.when(EDGE, UP_NORTH),
GBlockstate.variant(model_id, true, R180, R270))
.with(GBlockstate.when(EDGE, SOUTH_UP),
GBlockstate.variant(model_id, true, R180, R90));
}
@Override
public void setRecipe(RecipeExporter exporter) {
RecipeProvider.offerStonecuttingRecipe(exporter, RecipeCategory.BUILDING_BLOCKS, this, ReFramed.CUBE, 4);
ShapedRecipeJsonBuilder
.create(RecipeCategory.BUILDING_BLOCKS, this, 8)
.pattern("II")
.input('I', ReFramed.CUBE)
.criterion(FabricRecipeProvider.hasItem(ReFramed.CUBE), FabricRecipeProvider.conditionsFromItem(ReFramed.CUBE))
.criterion(FabricRecipeProvider.hasItem(this), FabricRecipeProvider.conditionsFromItem(this))
.offerTo(exporter);
}
static {
final VoxelShape STEP = createCuboidShape(0, 0, 0, 16, 8, 8);
STEP_VOXELS = VoxelListBuilder.create(STEP, 12)
.add(VoxelHelper::rotateCX)
.add(VoxelHelper::rotateCX)
.add(VoxelHelper::rotateCX)
.add(STEP, VoxelHelper::rotateCY)
.add(VoxelHelper::rotateZ)
.add(VoxelHelper::rotateZ)
.add(VoxelHelper::rotateZ)
.add(STEP, VoxelHelper::rotateCZ)
.add(VoxelHelper::rotateY)
.add(VoxelHelper::rotateY)
.add(VoxelHelper::rotateY)
.build();
}
}

View File

@@ -0,0 +1,136 @@
package fr.adrien1106.reframed.block;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.generator.BlockStateProvider;
import fr.adrien1106.reframed.generator.GBlockstate;
import fr.adrien1106.reframed.util.blocks.BlockHelper;
import fr.adrien1106.reframed.util.blocks.Edge;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext;
import net.minecraft.data.client.MultipartBlockStateSupplier;
import net.minecraft.data.server.recipe.RecipeExporter;
import net.minecraft.data.server.recipe.RecipeProvider;
import net.minecraft.data.server.recipe.ShapelessRecipeJsonBuilder;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.recipe.book.RecipeCategory;
import net.minecraft.state.StateManager;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Direction.Axis;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.BlockView;
import org.jetbrains.annotations.Nullable;
import static fr.adrien1106.reframed.block.ReFramedSlabBlock.getSlabShape;
import static fr.adrien1106.reframed.block.ReFramedStepBlock.getStepShape;
import static net.minecraft.data.client.VariantSettings.Rotation.*;
import static net.minecraft.state.property.Properties.AXIS;
import static net.minecraft.state.property.Properties.FACING;
import static net.minecraft.util.shape.VoxelShapes.empty;
public class ReFramedStepsSlabBlock extends WaterloggableReFramedDoubleBlock implements BlockStateProvider {
private record ModelCacheKey(Direction facing, Axis axis) {}
public ReFramedStepsSlabBlock(Settings settings) {
super(settings);
setDefaultState(getDefaultState().with(FACING, Direction.DOWN).with(AXIS, Axis.X));
}
@Override
public Object getModelCacheKey(BlockState state) {
return new ModelCacheKey(state.get(FACING), state.get(AXIS));
}
@Override
public int getModelStateCount() {
return 18;
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(builder.add(FACING, AXIS));
}
@Override
public @Nullable BlockState getPlacementState(ItemPlacementContext ctx) {
Vec3d pos = BlockHelper.getHitPos(ctx.getHitPos(), ctx.getBlockPos());
return super.getPlacementState(ctx)
.with(FACING, ctx.getSide().getOpposite())
.with(AXIS, BlockHelper.getHitAxis(pos, ctx.getSide()));
}
@Override
public VoxelShape getCullingShape(BlockState state, BlockView view, BlockPos pos) {
return isGhost(view, pos) ? empty() : getSlabShape(state.get(FACING));
}
@Override
public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return getSlabShape(state.get(FACING));
}
@Override
public VoxelShape getShape(BlockState state, int i) {
Axis axis = state.get(AXIS);
return getStepShape(Edge.getByDirections(
state.get(FACING),
switch (axis) {
case X -> i == 1 ? Direction.WEST : Direction.EAST;
case Y -> i == 1 ? Direction.DOWN : Direction.UP;
case Z -> i == 1 ? Direction.SOUTH : Direction.NORTH;
}
));
}
@Override
public int getTopThemeIndex(BlockState state) {
return 2;
}
@Override
public MultipartBlockStateSupplier getMultipart() {
Identifier step_id = ReFramed.id("steps_slab_special");
Identifier step_side_id = ReFramed.id("steps_slab_side_special");
return MultipartBlockStateSupplier.create(this)
.with(GBlockstate.when(FACING, Direction.DOWN, AXIS, Axis.X),
GBlockstate.variant(step_id, true, R0, R180))
.with(GBlockstate.when(FACING, Direction.DOWN, AXIS, Axis.Z),
GBlockstate.variant(step_id, true, R0, R90))
.with(GBlockstate.when(FACING, Direction.UP, AXIS, Axis.X),
GBlockstate.variant(step_id, true, R180, R180))
.with(GBlockstate.when(FACING, Direction.UP, AXIS, Axis.Z),
GBlockstate.variant(step_id, true, R180, R90))
.with(GBlockstate.when(FACING, Direction.EAST, AXIS, Axis.Z),
GBlockstate.variant(step_side_id, true, R180, R0))
.with(GBlockstate.when(FACING, Direction.EAST, AXIS, Axis.Y),
GBlockstate.variant(step_side_id, true, R90, R0))
.with(GBlockstate.when(FACING, Direction.SOUTH, AXIS, Axis.X),
GBlockstate.variant(step_side_id, true, R180, R90))
.with(GBlockstate.when(FACING, Direction.SOUTH, AXIS, Axis.Y),
GBlockstate.variant(step_side_id, true, R90, R90))
.with(GBlockstate.when(FACING, Direction.WEST, AXIS, Axis.Z),
GBlockstate.variant(step_side_id, true, R0, R180))
.with(GBlockstate.when(FACING, Direction.WEST, AXIS, Axis.Y),
GBlockstate.variant(step_side_id, true, R90, R180))
.with(GBlockstate.when(FACING, Direction.NORTH, AXIS, Axis.X),
GBlockstate.variant(step_side_id, true, R0, R270))
.with(GBlockstate.when(FACING, Direction.NORTH, AXIS, Axis.Y),
GBlockstate.variant(step_side_id, true, R90, R270));
}
@Override
public void setRecipe(RecipeExporter exporter) {
RecipeProvider.offerStonecuttingRecipe(exporter, RecipeCategory.BUILDING_BLOCKS, this, ReFramed.CUBE, 2);
ShapelessRecipeJsonBuilder
.create(RecipeCategory.BUILDING_BLOCKS, this)
.input(ReFramed.STEP, 2)
.criterion(FabricRecipeProvider.hasItem(ReFramed.CUBE), FabricRecipeProvider.conditionsFromItem(ReFramed.CUBE))
.criterion(FabricRecipeProvider.hasItem(this), FabricRecipeProvider.conditionsFromItem(this))
.offerTo(exporter);
}
}

View File

@@ -1,94 +0,0 @@
package fr.adrien1106.reframed.block;
import com.google.common.base.MoreObjects;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.util.ReFramedInteractionUtil;
import fr.adrien1106.reframed.util.ReframedInteractible;
import net.minecraft.block.Block;
import net.minecraft.block.BlockEntityProvider;
import net.minecraft.block.BlockSetType;
import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext;
import net.minecraft.block.TrapdoorBlock;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.item.ItemStack;
import net.minecraft.state.StateManager;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
import org.jetbrains.annotations.Nullable;
public class ReFramedTrapdoorBlock extends TrapdoorBlock implements BlockEntityProvider, ReframedInteractible {
public ReFramedTrapdoorBlock(Settings settings, BlockSetType woodType) {
super(woodType, settings);
setDefaultState(ReFramedInteractionUtil.setDefaultStates(getDefaultState()));
}
@Override
public @Nullable BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
return ReFramed.REFRAMED_BLOCK_ENTITY.instantiate(pos, state);
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(ReFramedInteractionUtil.appendProperties(builder));
}
@Nullable
@Override
public BlockState getPlacementState(ItemPlacementContext ctx) {
return ReFramedInteractionUtil.modifyPlacementState(super.getPlacementState(ctx), ctx);
}
@Override
public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
ActionResult r = ReFramedInteractionUtil.onUse(state, world, pos, player, hand, hit);
if(!r.isAccepted()) r = super.onUse(state, world, pos, player, hand, hit);
return r;
}
@Override
public void onStateReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) {
ReFramedInteractionUtil.onStateReplaced(state, world, pos, newState, moved);
super.onStateReplaced(state, world, pos, newState, moved);
}
@Override
public void onPlaced(World world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) {
ReFramedInteractionUtil.onPlaced(world, pos, state, placer, stack);
super.onPlaced(world, pos, state, placer, stack);
}
@Override
public VoxelShape getCollisionShape(BlockState state, BlockView view, BlockPos pos, ShapeContext ctx) {
return MoreObjects.firstNonNull(ReFramedInteractionUtil.getCollisionShape(state, view, pos, ctx), super.getCollisionShape(state, view, pos, ctx));
}
@Override
public boolean emitsRedstonePower(BlockState state) {
return ReFramedInteractionUtil.emitsRedstonePower(state);
}
@Override
public int getWeakRedstonePower(BlockState state, BlockView view, BlockPos pos, Direction dir) {
return ReFramedInteractionUtil.getWeakRedstonePower(state, view, pos, dir);
}
@Override
public int getStrongRedstonePower(BlockState state, BlockView view, BlockPos pos, Direction dir) {
return ReFramedInteractionUtil.getStrongRedstonePower(state, view, pos, dir);
}
@Override
public boolean canAddRedstoneEmission(BlockState state, BlockView view, BlockPos pos) {
return false;
}
}

View File

@@ -1,130 +0,0 @@
package fr.adrien1106.reframed.block;
import com.google.common.base.MoreObjects;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.util.ReFramedInteractionUtil;
import fr.adrien1106.reframed.mixin.WallBlockAccessor;
import net.minecraft.block.Block;
import net.minecraft.block.BlockEntityProvider;
import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext;
import net.minecraft.block.WallBlock;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.block.enums.WallShape;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.item.ItemStack;
import net.minecraft.state.StateManager;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
public class ReFramedWallBlock extends WallBlock implements BlockEntityProvider {
public ReFramedWallBlock(Settings settings) {
super(settings);
setDefaultState(ReFramedInteractionUtil.setDefaultStates(getDefaultState()));
initNewShapemaps(); //WallBlock specific haxx
}
@Nullable
@Override
public BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
return ReFramed.REFRAMED_BLOCK_ENTITY.instantiate(pos, state);
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(ReFramedInteractionUtil.appendProperties(builder));
}
@Nullable
@Override
public BlockState getPlacementState(ItemPlacementContext ctx) {
return ReFramedInteractionUtil.modifyPlacementState(super.getPlacementState(ctx), ctx);
}
@Override
public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
ActionResult r = ReFramedInteractionUtil.onUse(state, world, pos, player, hand, hit);
if(!r.isAccepted()) r = super.onUse(state, world, pos, player, hand, hit);
return r;
}
@Override
public void onStateReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) {
ReFramedInteractionUtil.onStateReplaced(state, world, pos, newState, moved);
super.onStateReplaced(state, world, pos, newState, moved);
}
@Override
public void onPlaced(World world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) {
ReFramedInteractionUtil.onPlaced(world, pos, state, placer, stack);
}
@Override
public VoxelShape getCollisionShape(BlockState state, BlockView view, BlockPos pos, ShapeContext ctx) {
return MoreObjects.firstNonNull(ReFramedInteractionUtil.getCollisionShape(state, view, pos, ctx), getNewShape(state, newCollisionShapeMap));
}
@Override
public VoxelShape getOutlineShape(BlockState state, BlockView view, BlockPos pos, ShapeContext ctx) {
return getNewShape(state, newShapeMap);
}
@Override
public boolean emitsRedstonePower(BlockState state) {
return ReFramedInteractionUtil.emitsRedstonePower(state);
}
@Override
public int getWeakRedstonePower(BlockState state, BlockView view, BlockPos pos, Direction dir) {
return ReFramedInteractionUtil.getWeakRedstonePower(state, view, pos, dir);
}
@Override
public int getStrongRedstonePower(BlockState state, BlockView view, BlockPos pos, Direction dir) {
return ReFramedInteractionUtil.getStrongRedstonePower(state, view, pos, dir);
}
//Shapemap heck (WallBlock has a map keyed on BlockState, but since we add more blockstates most of those map lookups fail)
protected record ShapeKey(boolean up, WallShape north, WallShape east, WallShape south, WallShape west) {
static ShapeKey fromBlockstate(BlockState state) {
return new ShapeKey(
state.get(WallBlock.UP),
state.get(WallBlock.NORTH_SHAPE),
state.get(WallBlock.EAST_SHAPE),
state.get(WallBlock.SOUTH_SHAPE),
state.get(WallBlock.WEST_SHAPE)
);
}
}
protected final Map<ShapeKey, VoxelShape> newShapeMap = new HashMap<>();
protected final Map<ShapeKey, VoxelShape> newCollisionShapeMap = new HashMap<>();
protected void initNewShapemaps() {
initNewShapemap(((WallBlockAccessor) this).getShapeMap(), newShapeMap);
initNewShapemap(((WallBlockAccessor) this).getCollisionShapeMap(), newCollisionShapeMap);
}
protected void initNewShapemap(Map<BlockState, VoxelShape> oldShapeMap, Map<ShapeKey, VoxelShape> newShapeMap) {
oldShapeMap.forEach((state, shape) -> newShapeMap.putIfAbsent(ShapeKey.fromBlockstate(state), shape));
}
protected VoxelShape getNewShape(BlockState state, Map<ShapeKey, VoxelShape> shapes) {
return shapes.getOrDefault(ShapeKey.fromBlockstate(state), VoxelShapes.empty());
}
}

View File

@@ -16,7 +16,6 @@ import org.jetbrains.annotations.Nullable;
public class WaterloggableReFramedBlock extends ReFramedBlock implements Waterloggable {
public WaterloggableReFramedBlock(Settings settings) {
super(settings);
setDefaultState(getDefaultState().with(Properties.WATERLOGGED, false));
}

View File

@@ -0,0 +1,43 @@
package fr.adrien1106.reframed.block;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Waterloggable;
import net.minecraft.fluid.FluidState;
import net.minecraft.fluid.Fluids;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.state.StateManager;
import net.minecraft.state.property.Properties;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.world.WorldAccess;
import org.jetbrains.annotations.Nullable;
public class WaterloggableReFramedDoubleBlock extends ReFramedDoubleBlock implements Waterloggable {
public WaterloggableReFramedDoubleBlock(Settings settings) {
super(settings);
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(builder.add(Properties.WATERLOGGED));
}
@Nullable
@Override
public BlockState getPlacementState(ItemPlacementContext ctx) {
BlockState sup = super.getPlacementState(ctx);
if(sup != null) sup = sup.with(Properties.WATERLOGGED, ctx.getWorld().getFluidState(ctx.getBlockPos()).isOf(Fluids.WATER));
return sup;
}
@Override
public FluidState getFluidState(BlockState state) {
return state.get(Properties.WATERLOGGED) ? Fluids.WATER.getStill(false) : super.getFluidState(state);
}
public BlockState getStateForNeighborUpdate(BlockState state, Direction direction, BlockState otherState, WorldAccess world, BlockPos pos, BlockPos moved) {
if(state.get(Properties.WATERLOGGED)) world.scheduleFluidTick(pos, Fluids.WATER, Fluids.WATER.getTickRate(world));
return super.getStateForNeighborUpdate(state, direction, otherState, world, pos, moved);
}
}

View File

@@ -25,73 +25,69 @@ public class ReFramedClient implements ClientModInitializer {
//all frames mustn't be on the SOLID layer because they are not opaque!
BlockRenderLayerMap.INSTANCE.putBlocks(RenderLayer.getCutout(), ReFramed.BLOCKS.toArray(new Block[0]));
HELPER.addReFramedModel(ReFramed.id("button_special") , HELPER.auto(new Identifier("block/button")));
HELPER.addReFramedModel(ReFramed.id("button_pressed_special") , HELPER.auto(new Identifier("block/button_pressed")));
HELPER.addReFramedModel(ReFramed.id("one_candle_special") , HELPER.auto(new Identifier("block/template_candle")));
HELPER.addReFramedModel(ReFramed.id("two_candles_special") , HELPER.auto(new Identifier("block/template_two_candles")));
HELPER.addReFramedModel(ReFramed.id("three_candles_special") , HELPER.auto(new Identifier("block/template_three_candles")));
HELPER.addReFramedModel(ReFramed.id("four_candles_special") , HELPER.auto(new Identifier("block/template_four_candles")));
HELPER.addReFramedModel(ReFramed.id("carpet_special") , HELPER.auto(new Identifier("block/carpet")));
HELPER.addReFramedModel(ReFramed.id("cube_special") , HELPER.auto(new Identifier("block/cube")));
HELPER.addReFramedModel(ReFramed.id("door_bottom_left_special") , HELPER.auto(new Identifier("block/door_bottom_left")));
HELPER.addReFramedModel(ReFramed.id("door_bottom_right_special") , HELPER.auto(new Identifier("block/door_bottom_right")));
HELPER.addReFramedModel(ReFramed.id("door_top_left_special") , HELPER.auto(new Identifier("block/door_top_left")));
HELPER.addReFramedModel(ReFramed.id("door_top_right_special") , HELPER.auto(new Identifier("block/door_top_right")));
HELPER.addReFramedModel(ReFramed.id("door_bottom_left_open_special"), HELPER.auto(new Identifier("block/door_bottom_left_open")));
HELPER.addReFramedModel(ReFramed.id("door_bottom_right_open_special"), HELPER.auto(new Identifier("block/door_bottom_right_open"))); //This is why we dont format code as tables kids
HELPER.addReFramedModel(ReFramed.id("door_top_left_open_special") , HELPER.auto(new Identifier("block/door_top_left_open")));
HELPER.addReFramedModel(ReFramed.id("door_top_right_open_special") , HELPER.auto(new Identifier("block/door_top_right_open")));
HELPER.addReFramedModel(ReFramed.id("fence_post_special") , HELPER.auto(new Identifier("block/fence_post")));
HELPER.addReFramedModel(ReFramed.id("fence_gate_special") , HELPER.auto(new Identifier("block/template_fence_gate")));
HELPER.addReFramedModel(ReFramed.id("fence_gate_open_special") , HELPER.auto(new Identifier("block/template_fence_gate_open")));
HELPER.addReFramedModel(ReFramed.id("fence_gate_wall_special") , HELPER.auto(new Identifier("block/template_fence_gate_wall")));
HELPER.addReFramedModel(ReFramed.id("fence_gate_wall_open_special") , HELPER.auto(new Identifier("block/template_fence_gate_wall_open")));
HELPER.addReFramedModel(ReFramed.id("glass_pane_post_special") , HELPER.auto(new Identifier("block/glass_pane_post")));
HELPER.addReFramedModel(ReFramed.id("glass_pane_noside_special") , HELPER.auto(new Identifier("block/glass_pane_noside")));
HELPER.addReFramedModel(ReFramed.id("glass_pane_noside_alt_special"), HELPER.auto(new Identifier("block/glass_pane_noside_alt")));
HELPER.addReFramedModel(ReFramed.id("pressure_plate_up_special") , HELPER.auto(new Identifier("block/pressure_plate_up")));
HELPER.addReFramedModel(ReFramed.id("pressure_plate_down_special") , HELPER.auto(new Identifier("block/pressure_plate_down")));
HELPER.addReFramedModel(ReFramed.id("slab_special") , HELPER.auto(new Identifier("block/slab")));
HELPER.addReFramedModel(ReFramed.id("stairs_special") , HELPER.auto(new Identifier("block/stairs")));
HELPER.addReFramedModel(ReFramed.id("double_outer_stairs_special") , HELPER.auto(ReFramed.id("block/double_outer_stairs")));
HELPER.addReFramedModel(ReFramed.id("inner_stairs_special") , HELPER.auto(new Identifier("block/inner_stairs")));
HELPER.addReFramedModel(ReFramed.id("outer_stairs_special") , HELPER.auto(new Identifier("block/outer_stairs")));
HELPER.addReFramedModel(ReFramed.id("outer_side_stairs_special") , HELPER.auto(ReFramed.id("block/outer_side_stairs")));
HELPER.addReFramedModel(ReFramed.id("trapdoor_bottom_special") , HELPER.auto(new Identifier("block/template_trapdoor_bottom")));
HELPER.addReFramedModel(ReFramed.id("trapdoor_top_special") , HELPER.auto(new Identifier("block/template_trapdoor_top")));
HELPER.addReFramedModel(ReFramed.id("wall_post_special") , HELPER.auto(new Identifier("block/template_wall_post")));
//vanilla style models (using "special-sprite replacement" method)
HELPER.addReFramedModel(ReFramed.id("lever_special") , HELPER.json(ReFramed.id("block/lever")));
HELPER.addReFramedModel(ReFramed.id("trapdoor_open_special") , HELPER.json(ReFramed.id("block/trapdoor_open")));
HELPER.addReFramedModel(ReFramed.id("lever_on_special") , HELPER.json(ReFramed.id("block/lever_on")));
//these next five only exist because AutoRetexturedModels don't seem to rotate their textures the right way when rotated from a multipart blockstate
HELPER.addReFramedModel(ReFramed.id("fence_side_special") , HELPER.json(ReFramed.id("block/fence_side")));
HELPER.addReFramedModel(ReFramed.id("glass_pane_side_special") , HELPER.json(ReFramed.id("block/glass_pane_side")));
HELPER.addReFramedModel(ReFramed.id("glass_pane_side_alt_special") , HELPER.json(ReFramed.id("block/glass_pane_side_alt")));
HELPER.addReFramedModel(ReFramed.id("wall_side_special") , HELPER.json(ReFramed.id("block/wall_side")));
HELPER.addReFramedModel(ReFramed.id("wall_side_tall_special") , HELPER.json(ReFramed.id("block/wall_side_tall")));
//item only models
HELPER.addReFramedModel(ReFramed.id("button_inventory_special") , HELPER.auto(new Identifier("block/button_inventory")));
HELPER.addReFramedModel(ReFramed.id("fence_inventory_special") , HELPER.auto(new Identifier("block/fence_inventory")));
HELPER.addReFramedModel(ReFramed.id("fence_post_inventory_special") , HELPER.auto(ReFramed.id("block/fence_post_inventory")));
HELPER.addReFramedModel(ReFramed.id("wall_inventory_special") , HELPER.auto(new Identifier("block/wall_inventory")));
// CUBE
HELPER.addReFramedModel("cube_special" , HELPER.auto(new Identifier("block/cube")));
// SMALL_CUBE
HELPER.addReFramedModel("small_cube_special" , HELPER.auto(ReFramed.id("block/small_cube/base")));
// SMALL_CUBES_STEP
HELPER.addReFramedModel("small_cubes_step_special" , HELPER.autoDouble(ReFramed.id("block/small_cube/base"), ReFramed.id("block/small_cube/step/base")));
HELPER.addReFramedModel("small_cubes_step_reverse_special" , HELPER.autoDouble(ReFramed.id("block/small_cube/step/base"), ReFramed.id("block/small_cube/base")));
// SLAB
HELPER.addReFramedModel("slab_special" , HELPER.auto(new Identifier("block/slab")));
// SLAB_CUBE
HELPER.addReFramedModel("double_slab_special" , HELPER.autoDouble(new Identifier("block/slab"), new Identifier("block/slab_top")));
// STAIR
HELPER.addReFramedModel("stair_special" , HELPER.auto(ReFramed.id("block/stair/straight")));
HELPER.addReFramedModel("outers_stair_special" , HELPER.auto(ReFramed.id("block/stair/double_outer")));
HELPER.addReFramedModel("inner_stair_special" , HELPER.auto(ReFramed.id("block/stair/inner")));
HELPER.addReFramedModel("outer_stair_special" , HELPER.auto(ReFramed.id("block/stair/outer")));
HELPER.addReFramedModel("outer_side_stair_special" , HELPER.auto(ReFramed.id("block/stair/outer_side")));
// STAIRS_CUBE
HELPER.addReFramedModel("stairs_cube_special" , HELPER.autoDouble(ReFramed.id("block/stair/straight"), ReFramed.id("block/stair/cube/straight")));
HELPER.addReFramedModel("outers_stairs_cube_special" , HELPER.autoDouble(ReFramed.id("block/stair/double_outer"), ReFramed.id("block/stair/cube/double_outer")));
HELPER.addReFramedModel("inner_stairs_cube_special" , HELPER.autoDouble(ReFramed.id("block/stair/inner"), ReFramed.id("block/stair/cube/inner")));
HELPER.addReFramedModel("outer_stairs_cube_special" , HELPER.autoDouble(ReFramed.id("block/stair/outer"), ReFramed.id("block/stair/cube/outer")));
HELPER.addReFramedModel("outer_side_stairs_cube_special" , HELPER.autoDouble(ReFramed.id("block/stair/outer_side"), ReFramed.id("block/stair/cube/outer_side")));
// HALF_STAIR
HELPER.addReFramedModel("half_stair_down_special" , HELPER.auto(ReFramed.id("block/half_stair/down")));
HELPER.addReFramedModel("half_stair_side_special" , HELPER.auto(ReFramed.id("block/half_stair/side")));
// HALF_STAIRS_SLAB
HELPER.addReFramedModel("half_stairs_slab_down_special" , HELPER.autoDouble(ReFramed.id("block/half_stair/down"), ReFramed.id("block/half_stair/slab/down")));
HELPER.addReFramedModel("half_stairs_slab_side_special" , HELPER.autoDouble(ReFramed.id("block/half_stair/side"), ReFramed.id("block/half_stair/slab/side")));
// HALF_STAIRS_STAIR
HELPER.addReFramedModel("half_stairs_stair_down_special" , HELPER.autoDouble(ReFramed.id("block/half_stair/down"), ReFramed.id("block/half_stair/stair/down")));
HELPER.addReFramedModel("half_stairs_stair_side_special" , HELPER.autoDouble(ReFramed.id("block/half_stair/side"), ReFramed.id("block/half_stair/stair/side")));
HELPER.addReFramedModel("half_stairs_stair_reverse_special" , HELPER.autoDouble(ReFramed.id("block/half_stair/stair/side"), ReFramed.id("block/half_stair/side")));
// STEP
HELPER.addReFramedModel("step_special" , HELPER.auto(ReFramed.id("block/step/down")));
// STEPS_SLAB
HELPER.addReFramedModel("steps_slab_special" , HELPER.autoDouble(ReFramed.id("block/step/down"), ReFramed.id("block/step/slab/down")));
HELPER.addReFramedModel("steps_slab_side_special" , HELPER.autoDouble(ReFramed.id("block/step/side"), ReFramed.id("block/step/slab/side")));
// LAYER
HELPER.addReFramedModel("layer_1_special" , HELPER.auto(new Identifier("block/snow_height2")));
HELPER.addReFramedModel("layer_2_special" , HELPER.auto(new Identifier("block/snow_height4")));
HELPER.addReFramedModel("layer_3_special" , HELPER.auto(new Identifier("block/snow_height6")));
HELPER.addReFramedModel("layer_4_special" , HELPER.auto(new Identifier("block/snow_height8")));
HELPER.addReFramedModel("layer_5_special" , HELPER.auto(new Identifier("block/snow_height10")));
HELPER.addReFramedModel("layer_6_special" , HELPER.auto(new Identifier("block/snow_height12")));
HELPER.addReFramedModel("layer_7_special" , HELPER.auto(new Identifier("block/snow_height14")));
HELPER.addReFramedModel("layer_8_special" , HELPER.auto(new Identifier("block/cube")));
//item model assignments (in lieu of models/item/___.json)
HELPER.assignItemModel(ReFramed.id("button_inventory_special") , ReFramed.BUTTON);
HELPER.assignItemModel(ReFramed.id("carpet_special") , ReFramed.CARPET);
HELPER.assignItemModel(ReFramed.id("cube_special") , ReFramed.CUBE);
HELPER.assignItemModel(ReFramed.id("fence_inventory_special") , ReFramed.FENCE);
HELPER.assignItemModel(ReFramed.id("fence_gate_special") , ReFramed.FENCE_GATE);
HELPER.assignItemModel(ReFramed.id("trapdoor_bottom_special") , ReFramed.IRON_TRAPDOOR);
HELPER.assignItemModel(ReFramed.id("fence_post_inventory_special") , ReFramed.POST);
HELPER.assignItemModel(ReFramed.id("pressure_plate_up_special") , ReFramed.PRESSURE_PLATE);
HELPER.assignItemModel(ReFramed.id("slab_special") , ReFramed.SLAB);
HELPER.assignItemModel(ReFramed.id("stairs_special") , ReFramed.STAIRS);
HELPER.assignItemModel(ReFramed.id("trapdoor_bottom_special") , ReFramed.TRAPDOOR);
HELPER.assignItemModel(ReFramed.id("wall_inventory_special") , ReFramed.WALL);
HELPER.assignItemModel("cube_special" , ReFramed.CUBE);
HELPER.assignItemModel("small_cube_special" , ReFramed.SMALL_CUBE);
HELPER.assignItemModel("small_cubes_step_special" , ReFramed.SMALL_CUBES_STEP);
HELPER.assignItemModel("slab_special" , ReFramed.SLAB);
HELPER.assignItemModel("double_slab_special" , ReFramed.SLABS_CUBE);
HELPER.assignItemModel("stair_special" , ReFramed.STAIR);
HELPER.assignItemModel("stairs_cube_special" , ReFramed.STAIRS_CUBE);
HELPER.assignItemModel("half_stair_down_special" , ReFramed.HALF_STAIR);
HELPER.assignItemModel("half_stairs_slab_down_special" , ReFramed.HALF_STAIRS_SLAB);
HELPER.assignItemModel("half_stairs_stair_down_special", ReFramed.HALF_STAIRS_STAIR);
HELPER.assignItemModel("step_special" , ReFramed.STEP);
HELPER.assignItemModel("steps_slab_special" , ReFramed.STEPS_SLAB);
HELPER.assignItemModel("layer_1_special" , ReFramed.LAYER);
}
private void privateInit() {

View File

@@ -1,8 +1,11 @@
package fr.adrien1106.reframed.client;
import fr.adrien1106.reframed.client.model.apperance.CamoAppearanceManager;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.client.model.UnbakedAutoRetexturedModel;
import fr.adrien1106.reframed.client.model.UnbakedDoubleRetexturedModel;
import fr.adrien1106.reframed.client.model.UnbakedJsonRetexturedModel;
import fr.adrien1106.reframed.client.model.UnbakedRetexturedModel;
import fr.adrien1106.reframed.client.model.apperance.CamoAppearanceManager;
import net.fabricmc.fabric.api.renderer.v1.Renderer;
import net.fabricmc.fabric.api.renderer.v1.RendererAccess;
import net.fabricmc.loader.api.FabricLoader;
@@ -23,20 +26,25 @@ public class ReFramedClientHelper {
private final ReFramedModelProvider prov;
public UnbakedModel auto(Identifier parent) {
public UnbakedRetexturedModel auto(Identifier parent) {
return new UnbakedAutoRetexturedModel(parent);
}
public UnbakedModel json(Identifier parent) {
public UnbakedRetexturedModel json(Identifier parent) {
return new UnbakedJsonRetexturedModel(parent);
}
public void addReFramedModel(Identifier id, UnbakedModel unbaked) {
prov.addReFramedModel(id, unbaked);
public UnbakedModel autoDouble(Identifier first, Identifier second) {
return new UnbakedDoubleRetexturedModel(auto(first), auto(second));
}
public void assignItemModel(Identifier model_id, ItemConvertible... item_convertibles) {
prov.assignItemModel(model_id, item_convertibles);
public void addReFramedModel(String id, UnbakedModel unbaked) {
prov.addReFramedModel(ReFramed.id(id), unbaked);
}
public void assignItemModel(String id, ItemConvertible... item_convertibles) {
prov.assignItemModel(ReFramed.id(id), item_convertibles);
}
public CamoAppearanceManager getCamoApperanceManager(Function<SpriteIdentifier, Sprite> spriteLookup) {

View File

@@ -37,9 +37,9 @@ public class ReFramedModelProvider implements ModelResourceProvider, ModelVarian
//but json models are never allowed to have non-json models as a parent, and frame unbaked models are not json models. Ah well.
//So, instead, we use a ModelVariantProvider to redirect attempts to load the item:id#inventory model.
@Override
public @Nullable UnbakedModel loadModelVariant(ModelIdentifier modelId, ModelProviderContext context) {
Identifier customModelId = itemAssignments.get(modelId);
return customModelId == null ? null : loadModelResource(customModelId, context);
public @Nullable UnbakedModel loadModelVariant(ModelIdentifier model, ModelProviderContext context) {
Identifier custom_model = itemAssignments.get(model);
return custom_model == null ? null : loadModelResource(custom_model, context);
}
/// camo appearance manager cache

View File

@@ -0,0 +1,50 @@
package fr.adrien1106.reframed.client.model;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.minecraft.block.BlockState;
import net.minecraft.client.texture.Sprite;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.random.Random;
import net.minecraft.world.BlockRenderView;
import java.util.List;
import java.util.function.Supplier;
@Environment(EnvType.CLIENT)
public class DoubleRetexturingBakedModel extends ForwardingBakedModel implements MultiRetexturableModel {
private final ForwardingBakedModel model_1, model_2;
public DoubleRetexturingBakedModel(ForwardingBakedModel model_1, ForwardingBakedModel model_2) {
this.wrapped = model_1.getWrappedModel();
this.model_1 = model_1;
this.model_2 = model_2;
}
@Override
public boolean isVanillaAdapter() {
return false;
}
@Override
public Sprite getParticleSprite() {
return model_1.getParticleSprite();
}
@Override
public void emitBlockQuads(BlockRenderView world, BlockState state, BlockPos pos, Supplier<Random> randomSupplier, RenderContext context) {}
@Override // models are emitted here because no checks are done on items
public void emitItemQuads(ItemStack stack, Supplier<Random> randomSupplier, RenderContext context) {
model_1.emitItemQuads(stack, randomSupplier, context);
model_2.emitItemQuads(stack, randomSupplier, context);
}
@Override
public List<ForwardingBakedModel> models() {
return List.of(model_1, model_2);
}
}

View File

@@ -0,0 +1,10 @@
package fr.adrien1106.reframed.client.model;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.BlockRenderView;
public interface DynamicBakedModel {
BakedModel computeQuads(BlockRenderView level, BlockState state, BlockPos pos, int theme_index);
}

View File

@@ -1,80 +0,0 @@
package fr.adrien1106.reframed.client.model;
import fr.adrien1106.reframed.client.ReFramedClient;
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
import net.fabricmc.fabric.api.renderer.v1.mesh.MeshBuilder;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.minecraft.client.render.model.ModelBakeSettings;
import net.minecraft.util.math.Direction;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.joml.Vector4f;
import java.util.EnumMap;
import java.util.Map;
public class MeshTransformUtil {
public static Mesh pretransformMesh(Mesh mesh, RenderContext.QuadTransform transform) {
MeshBuilder builder = ReFramedClient.HELPER.getFabricRenderer().meshBuilder();
QuadEmitter emitter = builder.getEmitter();
mesh.forEach(quad -> {
emitter.copyFrom(quad);
if(transform.transform(emitter)) emitter.emit();
});
return builder.build();
}
public static Map<Direction, Direction> facePermutation(Matrix4f mat) {
Map<Direction, Direction> facePermutation = new EnumMap<>(Direction.class);
for(Direction input : Direction.values()) {
Direction output = Direction.transform(mat, input);
facePermutation.put(input, output);
}
return facePermutation;
}
public static RenderContext.QuadTransform applyAffine(ModelBakeSettings settings) {
return applyMatrix(settings.getRotation().getMatrix());
}
public static RenderContext.QuadTransform applyMatrix(Matrix4f mat) {
Map<Direction, Direction> facePermutation = facePermutation(mat);
Vector3f pos3 = new Vector3f();
Vector4f pos4 = new Vector4f();
return quad -> {
//For each vertex:
for(int i = 0; i < 4; i++) {
//Copy pos into a vec3, then a vec4. the w component is set to 0 since this is a point, not a normal
quad.copyPos(i, pos3);
pos3.add(-0.5f, -0.5f, -0.5f);
pos4.set(pos3, 0);
//Compute the matrix-vector product. This function mutates the vec4 in-place.
//Note that `transformAffine` has the same purpose as `transform`; the difference is it
//assumes (without checking) that the last row of the matrix is 0,0,0,1, as an optimization
mat.transform(pos4);
//Manually copy the data back onto the vertex
quad.pos(i, pos4.x + 0.5f, pos4.y + 0.5f, pos4.z + 0.5f);
}
//permute tags
int tag = quad.tag();
if(tag != 0) quad.tag(facePermutation.get(RetexturingBakedModel.DIRECTIONS[tag - 1]).ordinal() + 1);
//permute lighting face (?)
quad.nominalFace(facePermutation.get(quad.lightFace()));
//permute cullface
Direction cull = quad.cullFace();
if(cull != null) quad.cullFace(facePermutation.get(cull));
//Output the quad
return true;
};
}
}

View File

@@ -0,0 +1,10 @@
package fr.adrien1106.reframed.client.model;
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
import java.util.List;
public interface MultiRetexturableModel {
List<ForwardingBakedModel> models();
}

View File

@@ -0,0 +1,93 @@
package fr.adrien1106.reframed.client.model;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.MathHelper;
import org.joml.Vector3f;
@Environment(EnvType.CLIENT)
public record QuadPosBounds(float min_x, float max_x, float min_y, float max_y, float min_z, float max_z) {
public static QuadPosBounds read(QuadView quad) {
return read(quad, true);
}
public static QuadPosBounds read(QuadView quad, boolean check_full) {
float x0 = quad.x(0), x1 = quad.x(1), x2 = quad.x(2), x3 = quad.x(3);
float y0 = quad.y(0), y1 = quad.y(1), y2 = quad.y(2), y3 = quad.y(3);
float z0 = quad.z(0), z1 = quad.z(1), z2 = quad.z(2), z3 = quad.z(3);
// Checks if the Dimensions are either 0 or 1 except for the Axis dimension
Direction.Axis axis = quad.nominalFace().getAxis();
if (check_full && (axis == Direction.Axis.X || (
(MathHelper.approximatelyEquals(x0, 0) || MathHelper.approximatelyEquals(x0, 1))
&& (MathHelper.approximatelyEquals(x1, 0) || MathHelper.approximatelyEquals(x1, 1))
&& (MathHelper.approximatelyEquals(x2, 0) || MathHelper.approximatelyEquals(x2, 1))
&& (MathHelper.approximatelyEquals(x3, 0) || MathHelper.approximatelyEquals(x3, 1))
)) && (axis == Direction.Axis.Y || (
(MathHelper.approximatelyEquals(y0, 0) || MathHelper.approximatelyEquals(y0, 1))
&& (MathHelper.approximatelyEquals(y1, 0) || MathHelper.approximatelyEquals(y1, 1))
&& (MathHelper.approximatelyEquals(y2, 0) || MathHelper.approximatelyEquals(y2, 1))
&& (MathHelper.approximatelyEquals(y3, 0) || MathHelper.approximatelyEquals(y3, 1))
)) & (axis == Direction.Axis.Z || (
(MathHelper.approximatelyEquals(z0, 0) || MathHelper.approximatelyEquals(z0, 1))
&& (MathHelper.approximatelyEquals(z1, 0) || MathHelper.approximatelyEquals(z1, 1))
&& (MathHelper.approximatelyEquals(z2, 0) || MathHelper.approximatelyEquals(z2, 1))
&& (MathHelper.approximatelyEquals(z3, 0) || MathHelper.approximatelyEquals(z3, 1))
))
) return null;
return new QuadPosBounds(
Math.min(Math.min(x0, x1), Math.min(x2, x3)),
Math.max(Math.max(x0, x1), Math.max(x2, x3)),
Math.min(Math.min(y0, y1), Math.min(y2, y3)),
Math.max(Math.max(y0, y1), Math.max(y2, y3)),
Math.min(Math.min(z0, z1), Math.min(z2, z3)),
Math.max(Math.max(z0, z1), Math.max(z2, z3))
);
}
public boolean matches(QuadPosBounds other_bounds) {
return !(
(min_x != max_x && (min_x >= other_bounds.max_x || max_x <= other_bounds.min_x))
|| (min_y != max_y && (min_y >= other_bounds.max_y || max_y <= other_bounds.min_y))
|| (min_z != max_z && (min_z >= other_bounds.max_z || max_z <= other_bounds.min_z))
);
}
public QuadPosBounds intersection(QuadPosBounds other_bounds, Direction.Axis axis) {
return new QuadPosBounds(
axis.equals(Direction.Axis.X) ? other_bounds.min_x: Math.max(min_x, other_bounds.min_x),
axis.equals(Direction.Axis.X) ? other_bounds.max_x: Math.min(max_x, other_bounds.max_x),
axis.equals(Direction.Axis.Y) ? other_bounds.min_y: Math.max(min_y, other_bounds.min_y),
axis.equals(Direction.Axis.Y) ? other_bounds.max_y: Math.min(max_y, other_bounds.max_y),
axis.equals(Direction.Axis.Z) ? other_bounds.min_z: Math.max(min_z, other_bounds.min_z),
axis.equals(Direction.Axis.Z) ? other_bounds.max_z: Math.min(max_z, other_bounds.max_z)
);
}
public void apply(MutableQuadView quad, QuadPosBounds origin_bounds) {
Vector3f pos = new Vector3f();
for (int i = 0; i < 4; i++) {
quad.copyPos(i, pos);
pos.x = MathHelper.approximatelyEquals(pos.x, origin_bounds.min_x)? min_x: max_x;
pos.y = MathHelper.approximatelyEquals(pos.y, origin_bounds.min_y)? min_y: max_y;
pos.z = MathHelper.approximatelyEquals(pos.z, origin_bounds.min_z)? min_z: max_z;
quad.pos(i, pos);
}
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof QuadPosBounds other)) return false;
return MathHelper.approximatelyEquals(min_x, other.min_x)
&& MathHelper.approximatelyEquals(min_y, other.min_y)
&& MathHelper.approximatelyEquals(min_z, other.min_z)
&& MathHelper.approximatelyEquals(max_x, other.max_x)
&& MathHelper.approximatelyEquals(max_y, other.max_y)
&& MathHelper.approximatelyEquals(max_z, other.max_z);
}
}

View File

@@ -1,14 +1,17 @@
package fr.adrien1106.reframed.client.model;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView;
import net.minecraft.client.texture.Sprite;
import net.minecraft.util.math.MathHelper;
record QuadUvBounds(float minU, float maxU, float minV, float maxV) {
static QuadUvBounds read(QuadView quad) {
float u0 = quad.u(0); float u1 = quad.u(1); float u2 = quad.u(2); float u3 = quad.u(3);
float v0 = quad.v(0); float v1 = quad.v(1); float v2 = quad.v(2); float v3 = quad.v(3);
@Environment(EnvType.CLIENT)
public record QuadUvBounds(float minU, float maxU, float minV, float maxV) {
public static QuadUvBounds read(QuadView quad) {
float u0 = quad.u(0), u1 = quad.u(1), u2 = quad.u(2), u3 = quad.u(3);
float v0 = quad.v(0), v1 = quad.v(1), v2 = quad.v(2), v3 = quad.v(3);
return new QuadUvBounds(
Math.min(Math.min(u0, u1), Math.min(u2, u3)),
Math.max(Math.max(u0, u1), Math.max(u2, u3)),
@@ -36,9 +39,5 @@ record QuadUvBounds(float minU, float maxU, float minV, float maxV) {
float value2 = MathHelper.clamp(value, low, high);
return (value2 - low) / (high - low);
}
//static float rangeRemap(float value, float low1, float high1, float low2, float high2) {
// float value2 = MathHelper.clamp(value, low1, high1);
// return low2 + (value2 - low1) * (high2 - low2) / (high1 - low1);
//}
}

View File

@@ -1,13 +1,15 @@
package fr.adrien1106.reframed.client.model;
import fr.adrien1106.reframed.block.ReFramedBlock;
import fr.adrien1106.reframed.block.ReFramedEntity;
import fr.adrien1106.reframed.client.ReFramedClient;
import fr.adrien1106.reframed.mixin.MinecraftAccessor;
import fr.adrien1106.reframed.client.model.apperance.CamoAppearance;
import fr.adrien1106.reframed.client.model.apperance.CamoAppearanceManager;
import fr.adrien1106.reframed.client.model.apperance.WeightedComputedAppearance;
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
import net.fabricmc.fabric.api.renderer.v1.mesh.*;
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.minecraft.block.BlockState;
@@ -22,35 +24,60 @@ import net.minecraft.util.math.Direction;
import net.minecraft.util.math.random.Random;
import net.minecraft.world.BlockRenderView;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
public abstract class RetexturingBakedModel extends ForwardingBakedModel {
public RetexturingBakedModel(BakedModel baseModel, CamoAppearanceManager tam, ModelBakeSettings settings, BlockState itemModelState, boolean ao) {
this.wrapped = baseModel; //field from the superclass; vanilla getQuads etc. will delegate through to this
this.tam = tam;
this.uvlock = settings.isUvLocked();
this.itemModelState = itemModelState;
public RetexturingBakedModel(BakedModel base_model, CamoAppearanceManager tam, int theme_index, ModelBakeSettings settings, BlockState item_state, boolean ao) {
this.wrapped = base_model; //field from the superclass; vanilla getQuads etc. will delegate through to this
this.appearance_manager = tam;
this.theme_index = theme_index;
this.uv_lock = settings.isUvLocked();
this.item_state = item_state;
this.ao = ao;
int cache_size = 64; // default is 64 why don't ask me and it should get overwritten
if (item_state.getBlock() instanceof ReFramedBlock frame_block) cache_size = frame_block.getModelStateCount() + 1;
BASE_MESH_CACHE = new Object2ObjectLinkedOpenHashMap<>(cache_size, 0.25f) {
@Override
protected void rehash(int v) {}
};
}
protected final CamoAppearanceManager tam;
protected final boolean uvlock;
protected final BlockState itemModelState;
protected final CamoAppearanceManager appearance_manager;
protected final int theme_index;
protected final boolean uv_lock;
protected final boolean ao;
protected record MeshCacheKey(BlockState state, TransformCacheKey transform) {}
protected final ConcurrentMap<MeshCacheKey, Mesh> retextured_meshes = new ConcurrentHashMap<>(); //mutable, append-only cache
protected record TransformCacheKey(CamoAppearance appearance, int model_id) {}
protected final ConcurrentMap<TransformCacheKey, RetexturingTransformer> retextured_transforms = new ConcurrentHashMap<>();
protected static final Direction[] DIRECTIONS = Direction.values();
protected static final Direction[] DIRECTIONS_AND_NULL = new Direction[DIRECTIONS.length + 1];
static { System.arraycopy(DIRECTIONS, 0, DIRECTIONS_AND_NULL, 0, DIRECTIONS.length); }
protected abstract Mesh getBaseMesh(BlockState state);
protected final BlockState item_state;
protected record MeshCacheKey(Object state_key, CamoAppearance appearance, int model_id) {}
/** cache that store retextured models */
protected final Object2ObjectLinkedOpenHashMap<MeshCacheKey, Mesh> RETEXTURED_MESH_CACHE =
new Object2ObjectLinkedOpenHashMap<>(128, 0.25f) {
@Override
protected void rehash(int v) {}
};
/** cache that stores the base meshes which has the size of the amount of models */
protected final Object2ObjectLinkedOpenHashMap<Object, Mesh> BASE_MESH_CACHE;
protected static final Direction[] DIRECTIONS_AND_NULL;
static {
Direction[] values = Direction.values();
DIRECTIONS_AND_NULL = new Direction[values.length + 1];
System.arraycopy(values, 0, DIRECTIONS_AND_NULL, 0, values.length);
}
protected Mesh getBaseMesh(Object key, BlockState state) {
//Convert models to re-texturable Meshes lazily, the first time we encounter each blockstate
if (BASE_MESH_CACHE.containsKey(key)) return BASE_MESH_CACHE.getAndMoveToFirst(key);
Mesh mesh = convertModel(state);
BASE_MESH_CACHE.putAndMoveToFirst(key, mesh);
return mesh;
}
protected abstract Mesh convertModel(BlockState state);
@Override
public boolean isVanillaAdapter() {
@@ -59,39 +86,42 @@ public abstract class RetexturingBakedModel extends ForwardingBakedModel {
@Override
public Sprite getParticleSprite() {
return tam.getDefaultAppearance().getSprite(Direction.UP, 0);
return appearance_manager.getDefaultAppearance(theme_index).getSprites(Direction.UP, 0).get(0).sprite();
}
@Override
public void emitBlockQuads(BlockRenderView blockView, BlockState state, BlockPos pos, Supplier<Random> randomSupplier, RenderContext context) {
BlockState theme = (blockView.getBlockEntityRenderData(pos) instanceof BlockState s) ? s : null;
public void emitBlockQuads(BlockRenderView world, BlockState state, BlockPos pos, Supplier<Random> randomSupplier, RenderContext context) {
// skip render if block not a frame (which should always be the case
if (!(state.getBlock() instanceof ReFramedBlock frame_block)) return;
BlockState theme = (world.getBlockEntity(pos) instanceof ThemeableBlockEntity s) ? s.getTheme(theme_index) : null;
QuadEmitter quad_emitter = context.getEmitter();
if(theme == null || theme.isAir()) {
getUntintedRetexturedMesh(new MeshCacheKey(state, new TransformCacheKey(tam.getDefaultAppearance(), 0)), 0).outputTo(quad_emitter);
getRetexturedMesh(
new MeshCacheKey(
frame_block.getModelCacheKey(state),
appearance_manager.getDefaultAppearance(theme_index),
0
),
state
).outputTo(quad_emitter);
return;
}
if(theme.getBlock() == Blocks.BARRIER) return;
CamoAppearance camo = tam.getCamoAppearance(theme);
CamoAppearance camo = appearance_manager.getCamoAppearance(world, theme, pos, theme_index, false);
long seed = theme.getRenderingSeed(pos);
int model_id = 0;
if (camo instanceof WeightedComputedAppearance wca) model_id = wca.getAppearanceIndex(seed);
int tint = 0xFF000000 | MinecraftClient.getInstance().getBlockColors().getColor(theme, blockView, pos, 0);
Mesh untintedMesh = getUntintedRetexturedMesh(
new MeshCacheKey(
state,
new TransformCacheKey(camo, model_id)
),
seed
);
int tint = 0xFF000000 | MinecraftClient.getInstance().getBlockColors().getColor(theme, world, pos, 0);
Mesh untintedMesh = getRetexturedMesh(new MeshCacheKey(frame_block.getModelCacheKey(state), camo, model_id), state);
//The specific tint might vary a lot; imagine grass color smoothly changing. Trying to bake the tint into
//the cached mesh will pollute it with a ton of single-use meshes with only slightly different colors.
if(tint == 0xFFFFFFFF) {
untintedMesh.outputTo(quad_emitter);
} else {
context.pushTransform(new TintingTransformer(camo, tint, seed));
context.pushTransform(new TintingTransformer(camo, model_id, tint));
untintedMesh.outputTo(quad_emitter);
context.popTransform();
}
@@ -101,79 +131,72 @@ public abstract class RetexturingBakedModel extends ForwardingBakedModel {
public void emitItemQuads(ItemStack stack, Supplier<Random> randomSupplier, RenderContext context) {
//cheeky: if the item has NBT data, pluck out the blockstate from it & look up the item color provider
//none of this is accessible unless you're in creative mode doing ctrl-pick btw
CamoAppearance nbtAppearance;
CamoAppearance appearance;
int tint;
BlockState theme = ReFramedEntity.readStateFromItem(stack);
BlockState theme = ReFramedEntity.readStateFromItem(stack, theme_index);
if(!theme.isAir()) {
nbtAppearance = tam.getCamoAppearance(theme);
appearance = appearance_manager.getCamoAppearance(null, theme, null, theme_index, true);
tint = 0xFF000000 | ((MinecraftAccessor) MinecraftClient.getInstance()).getItemColors().getColor(new ItemStack(theme.getBlock()), 0);
} else {
nbtAppearance = tam.getDefaultAppearance();
appearance = appearance_manager.getDefaultAppearance(theme_index);
tint = 0xFFFFFFFF;
}
Mesh untintedMesh = getUntintedRetexturedMesh(new MeshCacheKey(itemModelState, new TransformCacheKey(nbtAppearance, 0)), 0);
Mesh untintedMesh = getRetexturedMesh(new MeshCacheKey("I", appearance, 0), item_state);
QuadEmitter quad_emitter = context.getEmitter();
if(tint == 0xFFFFFFFF) {
untintedMesh.outputTo(quad_emitter);
} else {
context.pushTransform(new TintingTransformer(nbtAppearance, tint, 0));
context.pushTransform(new TintingTransformer(appearance, 0, tint));
untintedMesh.outputTo(quad_emitter);
context.popTransform();
}
}
protected Mesh getUntintedRetexturedMesh(MeshCacheKey key, long seed) {
return retextured_meshes.computeIfAbsent(key, (k) -> createUntintedRetexturedMesh(k, seed));
protected Mesh getRetexturedMesh(MeshCacheKey key, BlockState state) {
if (RETEXTURED_MESH_CACHE.containsKey(key)) return RETEXTURED_MESH_CACHE.getAndMoveToFirst(key);
Mesh mesh = transformMesh(key, state);
RETEXTURED_MESH_CACHE.putAndMoveToFirst(key, mesh);
return mesh;
}
protected Mesh createUntintedRetexturedMesh(MeshCacheKey key, long seed) {
RetexturingTransformer transformer = retextured_transforms.computeIfAbsent(key.transform, (k) -> new RetexturingTransformer(k.appearance, seed));
return MeshTransformUtil.pretransformMesh(getBaseMesh(key.state), transformer);
}
protected class RetexturingTransformer implements RenderContext.QuadTransform {
private final long seed;
protected RetexturingTransformer(CamoAppearance ta, long seed) {
this.ta = ta;
this.seed = seed;
}
protected final CamoAppearance ta;
@Override
public boolean transform(MutableQuadView quad) {
quad.material(ta.getRenderMaterial(ao));
int tag = quad.tag();
if(tag == 0) return true; //Pass the quad through unmodified.
//The quad tag numbers were selected so this magic trick works:
Direction direction = quad.nominalFace();
quad.spriteBake(ta.getSprite(direction, seed), MutableQuadView.BAKE_NORMALIZED | ta.getBakeFlags(direction, seed) | (uvlock ? MutableQuadView.BAKE_LOCK_UV : 0));
return true;
}
}
protected class TintingTransformer implements RenderContext.QuadTransform {
private final long seed;
protected TintingTransformer(CamoAppearance ta, int tint, long seed) {
this.ta = ta;
this.tint = tint;
this.seed = seed;
}
protected final CamoAppearance ta;
protected final int tint;
@Override
public boolean transform(MutableQuadView quad) {
int tag = quad.tag();
if(tag == 0) return true;
protected Mesh transformMesh(MeshCacheKey key, BlockState state) {
MeshBuilder builder = ReFramedClient.HELPER.getFabricRenderer().meshBuilder();
QuadEmitter emitter = builder.getEmitter();
AtomicInteger quad_index = new AtomicInteger();
getBaseMesh(key.state_key, state).forEach(quad -> {
int i = -1;
do {
emitter.copyFrom(quad);
i = key.appearance.transformQuad(emitter, i, quad_index.get(), key.model_id, ao, uv_lock);
} while (i > 0);
// kinda weird to do it like that but other directions don't use the quad_index so it doesn't matter
if (quad.cullFace() == null) quad_index.getAndIncrement();
});
return builder.build();
}
protected static class TintingTransformer implements RenderContext.QuadTransform {
private final CamoAppearance appearance;
private final int model_id;
private final int tint;
protected TintingTransformer(CamoAppearance appearance, int model_id, int tint) {
this.appearance = appearance;
this.model_id = model_id;
this.tint = tint;
}
@Override
public boolean transform(MutableQuadView quad) {
int camo_quad_index = quad.tag() - ((quad.tag() >>> 8) << 8);
if(camo_quad_index == 0) return true;
if(appearance.hasColor(quad.nominalFace(), model_id, camo_quad_index)) quad.color(tint, tint, tint, tint);
if(ta.hasColor(quad.nominalFace(), seed)) quad.color(tint, tint, tint, tint);
return true;
}
}

View File

@@ -12,7 +12,6 @@ import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.BakedQuad;
import net.minecraft.client.render.model.Baker;
import net.minecraft.client.render.model.ModelBakeSettings;
import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.util.SpriteIdentifier;
import net.minecraft.util.Identifier;
@@ -20,54 +19,31 @@ import net.minecraft.util.math.Direction;
import net.minecraft.util.math.random.Random;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
public class UnbakedAutoRetexturedModel implements UnbakedModel {
public class UnbakedAutoRetexturedModel extends UnbakedRetexturedModel {
public UnbakedAutoRetexturedModel(Identifier parent) {
this.parent = parent;
}
protected final Identifier parent;
protected BlockState itemModelState = Blocks.AIR.getDefaultState();
protected boolean ao = true;
@Override
public Collection<Identifier> getModelDependencies() {
return Collections.singletonList(parent);
}
@Override
public void setParents(Function<Identifier, UnbakedModel> function) {
function.apply(parent).setParents(function);
super(parent);
item_state = Blocks.AIR.getDefaultState();
}
@Nullable
@Override
public BakedModel bake(Baker baker, Function<SpriteIdentifier, Sprite> spriteLookup, ModelBakeSettings modelBakeSettings, Identifier identifier) {
public BakedModel bake(Baker baker, Function<SpriteIdentifier, Sprite> texture_getter, ModelBakeSettings bake_settings, Identifier identifier) {
return new RetexturingBakedModel(
baker.bake(parent, modelBakeSettings),
ReFramedClient.HELPER.getCamoApperanceManager(spriteLookup),
modelBakeSettings,
itemModelState,
baker.bake(parent, bake_settings),
ReFramedClient.HELPER.getCamoApperanceManager(texture_getter),
theme_index,
bake_settings,
item_state,
ao
) {
final ConcurrentMap<BlockState, Mesh> jsonToMesh = new ConcurrentHashMap<>();
@Override
protected Mesh getBaseMesh(BlockState state) {
//Convert models to retexturable Meshes lazily, the first time we encounter each blockstate
return jsonToMesh.computeIfAbsent(state, this::convertModel);
}
private Mesh convertModel(BlockState state) {
protected Mesh convertModel(BlockState state) {
Renderer r = ReFramedClient.HELPER.getFabricRenderer();
MeshBuilder builder = r.meshBuilder();
QuadEmitter emitter = builder.getEmitter();
RenderMaterial mat = tam.getCachedMaterial(state, false);
RenderMaterial mat = appearance_manager.getCachedMaterial(state, false);
Random rand = Random.create(42);

View File

@@ -0,0 +1,48 @@
package fr.adrien1106.reframed.client.model;
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.Baker;
import net.minecraft.client.render.model.ModelBakeSettings;
import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.util.SpriteIdentifier;
import net.minecraft.util.Identifier;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
public class UnbakedDoubleRetexturedModel implements UnbakedModel {
protected final UnbakedModel model_1;
protected final UnbakedModel model_2;
public UnbakedDoubleRetexturedModel(UnbakedRetexturedModel model_1, UnbakedRetexturedModel model_2) {
this.model_1 = model_1;
this.model_2 = model_2;
model_2.setThemeIndex(2);
}
@Override
public Collection<Identifier> getModelDependencies() {
return List.of(((List<Identifier>) model_1.getModelDependencies()).get(0), ((List<Identifier>) model_2.getModelDependencies()).get(0));
}
@Override
public void setParents(Function<Identifier, UnbakedModel> function) {
model_1.setParents(function);
model_2.setParents(function);
}
@Nullable
@Override
public BakedModel bake(Baker baker, Function<SpriteIdentifier, Sprite> texture_getter, ModelBakeSettings model_bake_settings, Identifier identifier) {
return new DoubleRetexturingBakedModel(
(ForwardingBakedModel) model_1.bake(baker, texture_getter, model_bake_settings, identifier),
(ForwardingBakedModel) model_2.bake(baker, texture_getter, model_bake_settings, identifier)
);
}
}

View File

@@ -8,7 +8,10 @@ import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
import net.fabricmc.fabric.api.renderer.v1.mesh.MeshBuilder;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.model.*;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.BakedQuad;
import net.minecraft.client.render.model.Baker;
import net.minecraft.client.render.model.ModelBakeSettings;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.util.SpriteIdentifier;
import net.minecraft.screen.PlayerScreenHandler;
@@ -17,65 +20,38 @@ import net.minecraft.util.math.Direction;
import net.minecraft.util.math.random.Random;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
public class UnbakedJsonRetexturedModel implements UnbakedModel {
public class UnbakedJsonRetexturedModel extends UnbakedRetexturedModel {
public UnbakedJsonRetexturedModel(Identifier parent) {
this.parent = parent;
}
protected final Identifier parent;
protected BlockState itemModelState;
protected boolean ao = true;
@Override
public Collection<Identifier> getModelDependencies() {
return Collections.singletonList(parent);
}
@Override
public void setParents(Function<Identifier, UnbakedModel> function) {
function.apply(parent).setParents(function);
super(parent);
}
@Nullable
@Override
public BakedModel bake(Baker baker, Function<SpriteIdentifier, Sprite> spriteLookup, ModelBakeSettings modelBakeSettings, Identifier identifier) {
Direction[] DIRECTIONS = RetexturingBakedModel.DIRECTIONS;
public BakedModel bake(Baker baker, Function<SpriteIdentifier, Sprite> spriteLookup, ModelBakeSettings bake_settings, Identifier identifier) {
Direction[] directions = Direction.values();
Sprite[] specialSprites = new Sprite[DIRECTIONS.length];
for(int i = 0; i < DIRECTIONS.length; i++) {
SpriteIdentifier id = new SpriteIdentifier(PlayerScreenHandler.BLOCK_ATLAS_TEXTURE, ReFramed.id("reframed_special/" + DIRECTIONS[i].getName()));
specialSprites[i] = Objects.requireNonNull(spriteLookup.apply(id), () -> "Couldn't find sprite " + id + " !");
Sprite[] sprites = new Sprite[directions.length];
for(int i = 0; i < directions.length; i++) {
SpriteIdentifier id = new SpriteIdentifier(PlayerScreenHandler.BLOCK_ATLAS_TEXTURE, ReFramed.id("reframed_special/" + directions[i].getName()));
sprites[i] = Objects.requireNonNull(spriteLookup.apply(id), () -> "Couldn't find sprite " + id + " !");
}
BakedModel model = baker.bake(parent, modelBakeSettings);
return new RetexturingBakedModel(
model,
baker.bake(parent, bake_settings),
ReFramedClient.HELPER.getCamoApperanceManager(spriteLookup),
modelBakeSettings,
itemModelState,
theme_index,
bake_settings,
item_state,
ao
) {
final ConcurrentMap<BlockState, Mesh> jsonToMesh = new ConcurrentHashMap<>();
@Override
protected Mesh getBaseMesh(BlockState state) {
//Convert models to re-texturable Meshes lazily, the first time we encounter each blockstate
return jsonToMesh.computeIfAbsent(state, this::convertModel);
}
private Mesh convertModel(BlockState state) {
protected Mesh convertModel(BlockState state) {
Renderer r = ReFramedClient.HELPER.getFabricRenderer();
MeshBuilder builder = r.meshBuilder();
QuadEmitter emitter = builder.getEmitter();
RenderMaterial mat = tam.getCachedMaterial(state, false);
RenderMaterial mat = appearance_manager.getCachedMaterial(state, false);
Random rand = Random.create(42);
@@ -84,9 +60,9 @@ public class UnbakedJsonRetexturedModel implements UnbakedModel {
emitter.fromVanilla(quad, mat, cullFace);
QuadUvBounds bounds = QuadUvBounds.read(emitter);
for(int i = 0; i < specialSprites.length; i++) {
if(bounds.displaysSprite(specialSprites[i])) {
bounds.normalizeUv(emitter, specialSprites[i]);
for(int i = 0; i < sprites.length; i++) {
if(bounds.displaysSprite(sprites[i])) {
bounds.normalizeUv(emitter, sprites[i]);
emitter.tag(i + 1);
break;
}

View File

@@ -0,0 +1,36 @@
package fr.adrien1106.reframed.client.model;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.util.Identifier;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
public abstract class UnbakedRetexturedModel implements UnbakedModel {
protected final Identifier parent;
protected int theme_index = 1;
protected BlockState item_state;
protected final boolean ao = true;
public UnbakedRetexturedModel(Identifier parent) {
this.parent = parent;
}
public void setThemeIndex(int theme_index) {
this.theme_index = theme_index;
}
@Override
public Collection<Identifier> getModelDependencies() {
return List.of(parent);
}
@Override
public void setParents(Function<Identifier, UnbakedModel> function) {
function.apply(parent).setParents(function);
}
}

View File

@@ -1,5 +1,8 @@
package fr.adrien1106.reframed.client.model.apperance;
import net.minecraft.client.texture.Sprite;
import net.minecraft.util.math.Direction;
public record Appearance(Sprite[] sprites, int[] flags, byte color_mask) {}
import java.util.List;
import java.util.Map;
public record Appearance(Map<Direction, List<SpriteProperties>> sprites) {}

View File

@@ -1,13 +1,80 @@
package fr.adrien1106.reframed.client.model.apperance;
import fr.adrien1106.reframed.client.model.QuadPosBounds;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
import net.minecraft.client.texture.Sprite;
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
import net.minecraft.util.math.Direction;
import org.jetbrains.annotations.NotNull;
public interface CamoAppearance {
@NotNull RenderMaterial getRenderMaterial(boolean ao);
@NotNull Sprite getSprite(Direction dir, long seed);
int getBakeFlags(Direction dir, long seed);
boolean hasColor(Direction dir, long seed);
import java.util.List;
@Environment(EnvType.CLIENT)
public abstract class CamoAppearance {
protected final int id;
protected final RenderMaterial ao_material;
protected final RenderMaterial material;
protected CamoAppearance(RenderMaterial ao_material, RenderMaterial material, int id) {
this.id = id;
this.ao_material = ao_material;
this.material = material;
}
public abstract @NotNull List<SpriteProperties> getSprites(Direction dir, int model_id);
public abstract boolean hasColor(Direction dir, int model_id, int index);
public @NotNull RenderMaterial getRenderMaterial(boolean ao) {
return ao && ao_material != null? ao_material : material;
}
public int transformQuad(QuadEmitter quad, int i, int quad_index, int model_id, boolean ao, boolean uv_lock) {
if(quad.tag() == 0) return 0; // Pass the quad through unmodified.
Direction direction = quad.nominalFace();
List<SpriteProperties> sprites = getSprites(direction, model_id);
if (i == -1) i = sprites.size();
SpriteProperties properties = sprites.get(sprites.size() - i);
int tag = i + (quad_index << 8);
i--;
QuadPosBounds bounds = properties.bounds();
if (bounds == null) { // sprite applies anywhere e.g. default behaviour
quad.material(getRenderMaterial(ao));
quad.spriteBake(
properties.sprite(),
MutableQuadView.BAKE_NORMALIZED
| properties.flags()
| (uv_lock ? MutableQuadView.BAKE_LOCK_UV : 0)
);
quad.tag(tag);
quad.emit();
return i;
}
// verify if sprite covers the current quad and apply the new size
QuadPosBounds origin_bounds = QuadPosBounds.read(quad, false);
if (!bounds.matches(origin_bounds)) return i;
// apply new quad shape
quad.material(getRenderMaterial(ao));
bounds.intersection(origin_bounds, direction.getAxis()).apply(quad, origin_bounds);
quad.spriteBake( // seems to work without the flags and break with it
properties.sprite(),
MutableQuadView.BAKE_NORMALIZED
| MutableQuadView.BAKE_LOCK_UV
);
quad.tag(tag);
quad.emit();
return i;
}
@Override
public int hashCode() {
return id;
}
}

View File

@@ -1,8 +1,14 @@
package fr.adrien1106.reframed.client.model.apperance;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
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.mixin.model.WeightedBakedModelAccessor;
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.BlendMode;
import net.fabricmc.fabric.api.renderer.v1.material.MaterialFinder;
@@ -21,17 +27,16 @@ import net.minecraft.client.util.SpriteIdentifier;
import net.minecraft.screen.PlayerScreenHandler;
import net.minecraft.util.Identifier;
import net.minecraft.util.collection.Weighted;
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 java.util.Arrays;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
@Environment(EnvType.CLIENT)
public class CamoAppearanceManager {
public CamoAppearanceManager(Function<SpriteIdentifier, Sprite> spriteLookup) {
@@ -39,41 +44,64 @@ public class CamoAppearanceManager {
for(BlendMode blend : BlendMode.values()) {
finder.clear().disableDiffuse(false).blendMode(blend);
materialsWithoutAo.put(blend, finder.ambientOcclusion(TriState.FALSE).find());
materialsWithAo.put(blend, finder.ambientOcclusion(TriState.DEFAULT).find()); //not "true" since that *forces* AO, i just want to *allow* AO
materials.put(blend, finder.ambientOcclusion(TriState.FALSE).find());
ao_materials.put(blend, finder.ambientOcclusion(TriState.DEFAULT).find()); //not "true" since that *forces* AO, i just want to *allow* AO
}
Sprite defaultSprite = spriteLookup.apply(DEFAULT_SPRITE_ID);
if(defaultSprite == null) throw new IllegalStateException("Couldn't locate " + DEFAULT_SPRITE_ID + " !");
this.defaultAppearance = new SingleSpriteAppearance(defaultSprite, materialsWithoutAo.get(BlendMode.CUTOUT), serialNumber.getAndIncrement());
Sprite barrier = spriteLookup.apply(BARRIER_SPRITE_ID);
if(barrier == null) barrier = defaultSprite; //eh
this.barrierItemAppearance = new SingleSpriteAppearance(barrier, materialsWithoutAo.get(BlendMode.CUTOUT), serialNumber.getAndIncrement());
Sprite sprite = spriteLookup.apply(DEFAULT_SPRITE_MAIN);
if(sprite == null) throw new IllegalStateException("Couldn't locate " + DEFAULT_SPRITE_MAIN + " !");
this.default_appearance = new SingleSpriteAppearance(sprite, materials.get(BlendMode.CUTOUT), serial_number.getAndIncrement());
sprite = spriteLookup.apply(DEFAULT_SPRITE_SECONDARY);
if(sprite == null) throw new IllegalStateException("Couldn't locate " + DEFAULT_SPRITE_MAIN + " !");
this.accent_appearance = new SingleSpriteAppearance(sprite, materials.get(BlendMode.CUTOUT), serial_number.getAndIncrement());
sprite = spriteLookup.apply(BARRIER_SPRITE_ID);
this.barrierItemAppearance = new SingleSpriteAppearance(sprite, materials.get(BlendMode.CUTOUT), serial_number.getAndIncrement());
}
protected static final SpriteIdentifier DEFAULT_SPRITE_ID = new SpriteIdentifier(PlayerScreenHandler.BLOCK_ATLAS_TEXTURE, new Identifier(ReFramed.MODID, "block/framed_block"));
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 defaultAppearance;
private final CamoAppearance default_appearance;
private final CamoAppearance accent_appearance;
private final CamoAppearance barrierItemAppearance;
private static final Cache<BlockState, CamoAppearance> APPEARANCE_CACHE = CacheBuilder.newBuilder().maximumSize(2048).build();
private final AtomicInteger serial_number = new AtomicInteger(0); //Mutable
private final ConcurrentHashMap<BlockState, CamoAppearance> appearanceCache = new ConcurrentHashMap<>(); //Mutable, append-only cache
private final AtomicInteger serialNumber = new AtomicInteger(0); //Mutable
private final EnumMap<BlendMode, RenderMaterial> ao_materials = new EnumMap<>(BlendMode.class);
private final EnumMap<BlendMode, RenderMaterial> materials = new EnumMap<>(BlendMode.class); //Immutable contents
private final EnumMap<BlendMode, RenderMaterial> materialsWithAo = new EnumMap<>(BlendMode.class);
private final EnumMap<BlendMode, RenderMaterial> materialsWithoutAo = new EnumMap<>(BlendMode.class); //Immutable contents
public CamoAppearance getDefaultAppearance() {
return defaultAppearance;
public CamoAppearance getDefaultAppearance(int appearance) {
return appearance == 2 ? accent_appearance: default_appearance;
}
public CamoAppearance getCamoAppearance(BlockState state) {
return appearanceCache.computeIfAbsent(state, this::computeAppearance);
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
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;
}
// refresh cache
if (APPEARANCE_CACHE.asMap().containsKey(state)) return APPEARANCE_CACHE.getIfPresent(state);
CamoAppearance appearance = computeAppearance(model, state);
APPEARANCE_CACHE.put(state, appearance);
return appearance;
}
public RenderMaterial getCachedMaterial(BlockState state, boolean ao) {
Map<BlendMode, RenderMaterial> m = ao ? materialsWithAo : materialsWithoutAo;
Map<BlendMode, RenderMaterial> m = ao ? ao_materials : materials;
return m.get(BlendMode.fromRenderLayer(RenderLayers.getBlockLayer(state)));
}
@@ -81,16 +109,15 @@ public class CamoAppearanceManager {
// The computeIfAbsent map update will work without corrupting the map, but there will be some "wasted effort" computing the value twice.
// The results are going to be the same, apart from their serialNumbers differing (= their equals & hashCode differing).
// Tiny amount of wasted space in some caches if CamoAppearances are used as a map key, then. IMO it's not a critical issue.
private CamoAppearance computeAppearance(BlockState state) {
private CamoAppearance computeAppearance(BakedModel model, BlockState state) {
if(state.getBlock() == Blocks.BARRIER) return barrierItemAppearance;
BakedModel model = MinecraftClient.getInstance().getBlockRenderManager().getModel(state);
if (!(model instanceof WeightedBakedModelAccessor weighted_model)) {
return new ComputedAppearance(
getAppearance(model),
getCachedMaterial(state, true),
getCachedMaterial(state, false),
serialNumber.getAndIncrement()
serial_number.getAndIncrement()
);
}
List<Weighted.Present<Appearance>> appearances = weighted_model.getModels().stream()
@@ -101,7 +128,7 @@ public class CamoAppearanceManager {
appearances,
getCachedMaterial(state, true),
getCachedMaterial(state, false),
serialNumber.getAndIncrement()
serial_number.getAndIncrement()
);
}
@@ -112,42 +139,34 @@ public class CamoAppearanceManager {
RenderMaterial material = r.materialFinder().clear().find();
Random random = Random.create();
Sprite[] sprites = new Sprite[6];
int[] flags = new int[6];
byte[] color_mask = {0b000000};
Map<Direction, List<SpriteProperties>> sprites = new EnumMap<>(Direction.class);
//Read quads off the model by their `cullface`
Arrays.stream(Direction.values()).forEach(direction -> {
List<BakedQuad> quads = model.getQuads(null, direction, random);
if(quads.isEmpty() || quads.get(0) == null) {
sprites[direction.ordinal()] = defaultAppearance.getSprite(direction, 0); // make sure direction has a sprite
if(quads.isEmpty()) { // add default appearance if none present
sprites.put(direction, default_appearance.getSprites(direction, 0));
return;
}
BakedQuad quad = quads.get(0);
if(quad.hasColor()) color_mask[0] |= (byte) (1 << direction.ordinal());
Sprite sprite = quad.getSprite();
if(sprite == null) return;
sprites[direction.ordinal()] = sprite;
//Problem: Some models (eg. pistons, stone, glazed terracotta) have their UV coordinates permuted in
//non-standard ways. The actual png image appears sideways, but the original model's UVs rotate it right way up again.
//If I simply display the texture on my model, it will appear sideways, like it does in the png.
//If I can discover the pattern of rotations and flips on the original model, I can "un-rotate" the texture back
//into a standard orientation.
//
//Solution: Look at the first and second vertices of the orignial quad, and decide whether their UV coordinates
//are "low" or "high" compared to the middle of the sprite. The first two vertices uniquely determine the pattern
//of the other two (since UV coordinates are unique and shouldn't cross). There are 16 possibilities so this information
//is easily summarized in a bitfield, and the correct fabric rendering API "bake flags" to un-transform the sprite
//are looked up with a simple table.
quad_emitter.fromVanilla(quad, material, direction);
flags[direction.ordinal()] = getBakeFlags(quad_emitter, sprite);
sprites.put(direction, new ArrayList<>());
quads.forEach(quad -> {
Sprite sprite = quad.getSprite();
if(sprite == null) return;
sprites.computeIfPresent(direction, (dir, pairs) -> {
quad_emitter.fromVanilla(quad, material, direction);
pairs.add(new SpriteProperties(
sprite,
getBakeFlags(quad_emitter, sprite),
QuadPosBounds.read(quad_emitter),
quad.hasColor())
);
return pairs;
});
});
});
return new Appearance(sprites, flags, color_mask[0]);
return new Appearance(sprites);
}
private static int getBakeFlags(QuadEmitter emitter, Sprite sprite) {

View File

@@ -1,62 +1,38 @@
package fr.adrien1106.reframed.client.model.apperance;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
import net.minecraft.client.texture.Sprite;
import net.minecraft.util.math.Direction;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.List;
public class ComputedAppearance implements CamoAppearance {
@Environment(EnvType.CLIENT)
public class ComputedAppearance extends CamoAppearance {
private final Appearance appearance;
private final int id;
private final RenderMaterial matWithAo;
private final RenderMaterial matWithoutAo;
public ComputedAppearance(@NotNull Appearance appearance, RenderMaterial withAo, RenderMaterial withoutAo, int id) {
public ComputedAppearance(@NotNull Appearance appearance, RenderMaterial ao_material, RenderMaterial material, int id) {
super(ao_material, material, id);
this.appearance = appearance;
this.id = id;
this.matWithAo = withAo;
this.matWithoutAo = withoutAo;
}
@Override
public @NotNull RenderMaterial getRenderMaterial(boolean ao) {
return ao ? matWithAo : matWithoutAo;
public @NotNull List<SpriteProperties> getSprites(Direction dir, int model_id) {
return appearance.sprites().get(dir);
}
@Override
public @NotNull Sprite getSprite(Direction dir, long seed) {
return appearance.sprites()[dir.ordinal()];
}
@Override
public int getBakeFlags(Direction dir, long seed) {
return appearance.flags()[dir.ordinal()];
}
@Override
public boolean hasColor(Direction dir, long seed) {
return (appearance.color_mask() & (1 << dir.ordinal())) != 0;
public boolean hasColor(Direction dir, int model_id, int index) {
List<SpriteProperties> properties = getSprites(dir, model_id);
if (index != 0) index = properties.size() - index;
return properties.get(index).has_colors();
}
@Override
public boolean equals(Object o) {
if(this == o) return true;
if(o == null || getClass() != o.getClass()) return false;
ComputedAppearance that = (ComputedAppearance) o;
if(!(o instanceof ComputedAppearance that)) return false;
return id == that.id;
}
@Override
public int hashCode() {
return id;
}
@Override
public String toString() {
return "ComputedApperance{sprites=%s, bakeFlags=%s, hasColorMask=%s, matWithoutAo=%s, matWithAo=%s, id=%d}"
.formatted(Arrays.toString(appearance.sprites()), Arrays.toString(appearance.flags()), appearance.color_mask(), matWithoutAo, matWithAo, id);
}
}

View File

@@ -1,56 +1,42 @@
package fr.adrien1106.reframed.client.model.apperance;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
import net.minecraft.client.texture.Sprite;
import net.minecraft.util.math.Direction;
import org.jetbrains.annotations.NotNull;
public class SingleSpriteAppearance implements CamoAppearance {
import java.util.List;
@Environment(EnvType.CLIENT)
public class SingleSpriteAppearance extends CamoAppearance {
private final @NotNull Sprite defaultSprite;
private final RenderMaterial mat;
private final int id;
public SingleSpriteAppearance(@NotNull Sprite defaultSprite, RenderMaterial mat, int id) {
super(null, mat, id);
this.defaultSprite = defaultSprite;
this.mat = mat;
this.id = id;
}
@Override
public @NotNull RenderMaterial getRenderMaterial(boolean ao) {
return mat;
public @NotNull List<SpriteProperties> getSprites(Direction dir, int model_id) {
return List.of(new SpriteProperties(defaultSprite, 0, null, false));
}
@Override
public @NotNull Sprite getSprite(Direction dir, long seed) {
return defaultSprite;
}
@Override
public int getBakeFlags(Direction dir, long seed) {
return 0;
}
@Override
public boolean hasColor(Direction dir, long seed) {
public boolean hasColor(Direction dir, int model_id, int index) {
return false;
}
@Override
public boolean equals(Object o) {
if(this == o) return true;
if(o == null || getClass() != o.getClass()) return false;
SingleSpriteAppearance that = (SingleSpriteAppearance) o;
if(!(o instanceof SingleSpriteAppearance that)) return false;
return id == that.id;
}
@Override
public int hashCode() {
return id;
}
@Override
public String toString() {
return "SingleSpriteAppearance[defaultSprite=%s, mat=%s, id=%d]".formatted(defaultSprite, mat, id);
return "SingleSpriteAppearance[defaultSprite=%s, mat=%s, id=%d]".formatted(defaultSprite, material, id);
}
}

View File

@@ -0,0 +1,7 @@
package fr.adrien1106.reframed.client.model.apperance;
import fr.adrien1106.reframed.client.model.QuadPosBounds;
import net.minecraft.client.texture.Sprite;
public record SpriteProperties(Sprite sprite, int flags, QuadPosBounds bounds, boolean has_colors) {
}

View File

@@ -1,35 +1,25 @@
package fr.adrien1106.reframed.client.model.apperance;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
import net.minecraft.client.texture.Sprite;
import net.minecraft.util.collection.Weighted;
import net.minecraft.util.collection.Weighting;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.random.Random;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.List;
public class WeightedComputedAppearance implements CamoAppearance {
@Environment(EnvType.CLIENT)
public class WeightedComputedAppearance extends CamoAppearance {
private final List<Weighted.Present<Appearance>> appearances;
private final int total_weight;
private final int id;
private final RenderMaterial matWithAo;
private final RenderMaterial matWithoutAo;
public WeightedComputedAppearance(@NotNull List<Weighted.Present<Appearance>> appearances, RenderMaterial withAo, RenderMaterial withoutAo, int id) {
public WeightedComputedAppearance(@NotNull List<Weighted.Present<Appearance>> appearances, RenderMaterial ao_material, RenderMaterial material, int id) {
super(ao_material, material, id);
this.appearances = appearances;
this.total_weight = Weighting.getWeightSum(appearances);
this.id = id;
this.matWithAo = withAo;
this.matWithoutAo = withoutAo;
}
@Override
public @NotNull RenderMaterial getRenderMaterial(boolean ao) {
return ao ? matWithAo : matWithoutAo;
}
@@ -39,44 +29,27 @@ public class WeightedComputedAppearance implements CamoAppearance {
.map(appearances::indexOf).orElse(0);
}
private Appearance getAppearance(long seed) {
Random random = Random.create(seed);
return Weighting.getAt(appearances, Math.abs((int)random.nextLong()) % total_weight)
.map(Weighted.Present::getData).get();
private Appearance getAppearance(int model_id) {
return appearances.get(model_id).getData();
}
@Override
public @NotNull Sprite getSprite(Direction dir, long seed) {
return getAppearance(seed).sprites()[dir.ordinal()];
public @NotNull List<SpriteProperties> getSprites(Direction dir, int model_id) {
return getAppearance(model_id).sprites().get(dir);
}
@Override
public int getBakeFlags(Direction dir, long seed) {
return getAppearance(seed).flags()[dir.ordinal()];
}
@Override
public boolean hasColor(Direction dir, long seed) {
return (getAppearance(seed).color_mask() & (1 << dir.ordinal())) != 0;
public boolean hasColor(Direction dir, int model_id, int index) {
List<SpriteProperties> properties = getSprites(dir, model_id);
if (index != 0) index = properties.size() - index;
return properties.get(index).has_colors();
}
@Override
public boolean equals(Object o) {
if(this == o) return true;
if(o == null || getClass() != o.getClass()) return false;
WeightedComputedAppearance that = (WeightedComputedAppearance) o;
if(!(o instanceof WeightedComputedAppearance that)) return false;
return id == that.id;
}
@Override
public int hashCode() {
return id;
}
@Override
public String toString() {
Appearance appearance = appearances.get(0).getData();
return "ComputedApperance{sprites=%s, bakeFlags=%s, hasColorMask=%s, matWithoutAo=%s, matWithAo=%s, id=%d}"
.formatted(Arrays.toString(appearance.sprites()), Arrays.toString(appearance.flags()), appearance.color_mask(), matWithoutAo, matWithAo, id);
}
}

View File

@@ -0,0 +1,61 @@
package fr.adrien1106.reframed.compat;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.BakedQuad;
import net.minecraft.client.render.model.json.ModelOverrideList;
import net.minecraft.client.render.model.json.ModelTransformation;
import net.minecraft.client.texture.Sprite;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.random.Random;
import java.util.List;
import java.util.Map;
public class RebakedAthenaModel implements BakedModel {
protected final Map<Direction, List<BakedQuad>> face_quads;
public RebakedAthenaModel(Map<Direction, List<BakedQuad>> face_quads) {
this.face_quads = face_quads;
}
@Override
public List<BakedQuad> getQuads(BlockState state, Direction direction, Random random) {
return face_quads.getOrDefault(direction, List.of());
}
@Override
public boolean useAmbientOcclusion() {
return false;
}
@Override
public boolean hasDepth() {
return false;
}
@Override
public boolean isSideLit() {
return false;
}
@Override
public boolean isBuiltin() {
return false;
}
@Override
public Sprite getParticleSprite() {
return null;
}
@Override
public ModelTransformation getTransformation() {
return null;
}
@Override
public ModelOverrideList getOverrides() {
return null;
}
}

View File

@@ -0,0 +1,7 @@
package fr.adrien1106.reframed.generator;
import net.minecraft.data.client.BlockStateSupplier;
public interface BlockStateProvider {
BlockStateSupplier getMultipart();
}

View File

@@ -0,0 +1,44 @@
package fr.adrien1106.reframed.generator;
import fr.adrien1106.reframed.ReFramed;
import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricAdvancementProvider;
import net.minecraft.advancement.Advancement;
import net.minecraft.advancement.AdvancementEntry;
import net.minecraft.advancement.AdvancementFrame;
import net.minecraft.advancement.AdvancementRewards;
import net.minecraft.advancement.criterion.InventoryChangedCriterion;
import net.minecraft.item.Items;
import net.minecraft.registry.Registries;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import java.util.function.Consumer;
public class GAdvancement extends FabricAdvancementProvider {
protected GAdvancement(FabricDataOutput output) {
super(output);
}
@Override
public void generateAdvancement(Consumer<AdvancementEntry> consumer) {
Advancement.Builder builder = Advancement.Builder.create()
.display(
Items.CAKE,
Text.literal("Is Everything A Lie ?"),
Text.translatable("advancements.reframed.description"),
new Identifier("textures/gui/advancements/backgrounds/adventure.png"),
AdvancementFrame.TASK,
true,
true,
false
).rewards(AdvancementRewards.Builder.experience(1000));
ReFramed.BLOCKS.forEach(block ->
builder.criterion(
"get_" + Registries.BLOCK.getId(block).getPath(),
InventoryChangedCriterion.Conditions.items(block)
)
);
builder.build(consumer, ReFramed.MODID + "/root");
}
}

View File

@@ -1,5 +1,6 @@
package fr.adrien1106.reframed.generator;
import fr.adrien1106.reframed.ReFramed;
import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricBlockLootTableProvider;
import net.minecraft.registry.Registries;
@@ -12,6 +13,6 @@ public class GBlockLoot extends FabricBlockLootTableProvider {
@Override
public void generate() {
Generator.BLOCKS.forEach(block -> addDrop(block, Registries.ITEM.get(Registries.BLOCK.getId(block))));
ReFramed.BLOCKS.forEach(block -> addDrop(block, Registries.ITEM.get(Registries.BLOCK.getId(block))));
}
}

View File

@@ -0,0 +1,22 @@
package fr.adrien1106.reframed.generator;
import fr.adrien1106.reframed.ReFramed;
import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricTagProvider.BlockTagProvider;
import net.minecraft.registry.RegistryWrapper.WrapperLookup;
import net.minecraft.registry.tag.BlockTags;
import java.util.concurrent.CompletableFuture;
public class GBlockTag extends BlockTagProvider {
public GBlockTag(FabricDataOutput output, CompletableFuture<WrapperLookup> registries) {
super(output, registries);
}
@Override
protected void configure(WrapperLookup arg) {
FabricTagBuilder builder = getOrCreateTagBuilder(BlockTags.AXE_MINEABLE);
ReFramed.BLOCKS.forEach(builder::add);
}
}

View File

@@ -1,5 +1,6 @@
package fr.adrien1106.reframed.generator;
import fr.adrien1106.reframed.ReFramed;
import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricModelProvider;
import net.minecraft.data.client.*;
@@ -8,6 +9,8 @@ import net.minecraft.util.Identifier;
import java.util.Objects;
import static net.minecraft.data.client.VariantSettings.Rotation.R0;
public class GBlockstate extends FabricModelProvider {
public GBlockstate(FabricDataOutput output) {
@@ -16,10 +19,20 @@ public class GBlockstate extends FabricModelProvider {
@Override
public void generateBlockStateModels(BlockStateModelGenerator model_generator) {
Generator.BLOCKS
ReFramed.BLOCKS
.forEach(model_generator::excludeFromSimpleItemModelGeneration);
Generator.BLOCKS.stream()
.map(block -> block instanceof MultipartBlockStateProvider multipart_block ? multipart_block.getMultipart(): null)
ReFramed.BLOCKS.stream()
.map(block -> {
if (block instanceof BlockStateProvider multipart_block) return multipart_block.getMultipart();
return VariantsBlockStateSupplier.create(
block,
GBlockstate.variant(
ReFramed.id("cube_special"),
true,
R0, R0
)
);
})
.filter(Objects::nonNull)
.forEach(model_generator.blockStateCollector);
}
@@ -30,8 +43,8 @@ public class GBlockstate extends FabricModelProvider {
public static BlockStateVariant variant(Identifier model, boolean uv_lock, VariantSettings.Rotation x, VariantSettings.Rotation y) {
BlockStateVariant variant = BlockStateVariant.create().put(VariantSettings.MODEL, model);
if (uv_lock) variant.put(VariantSettings.UVLOCK, uv_lock);
if (!x.equals(VariantSettings.Rotation.R0)) variant.put(VariantSettings.X, x);
if (!y.equals(VariantSettings.Rotation.R0)) variant.put(VariantSettings.Y, y);
if (!x.equals(R0)) variant.put(VariantSettings.X, x);
if (!y.equals(R0)) variant.put(VariantSettings.Y, y);
return variant;
}

View File

@@ -0,0 +1,28 @@
package fr.adrien1106.reframed.generator;
import fr.adrien1106.reframed.ReFramed;
import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricLanguageProvider;
import net.minecraft.registry.Registries;
import java.util.Arrays;
import java.util.stream.Collectors;
public class GLanguage extends FabricLanguageProvider {
protected GLanguage(FabricDataOutput dataOutput) {
super(dataOutput, "en_us");
}
@Override
public void generateTranslations(TranslationBuilder builder) {
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"));
}
private static String beautify(String name) {
return Arrays.stream(name.split("_"))
.map(word -> word.substring(0, 1).toUpperCase() + word.substring(1))
.collect(Collectors.joining(" "));
}
}

View File

@@ -0,0 +1,19 @@
package fr.adrien1106.reframed.generator;
import fr.adrien1106.reframed.ReFramed;
import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider;
import net.minecraft.data.server.recipe.RecipeExporter;
public class GRecipe extends FabricRecipeProvider {
public GRecipe(FabricDataOutput output) {
super(output);
}
@Override
public void generate(RecipeExporter exporter) {
ReFramed.BLOCKS.forEach(block -> {
if (block instanceof RecipeSetter provider) provider.setRecipe(exporter);
});
}
}

View File

@@ -2,22 +2,17 @@ package fr.adrien1106.reframed.generator;
import net.fabricmc.fabric.api.datagen.v1.DataGeneratorEntrypoint;
import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator;
import net.minecraft.block.Block;
import java.util.List;
import static fr.adrien1106.reframed.ReFramed.*;
public class Generator implements DataGeneratorEntrypoint {
/**
* missing DOOR, IRON_DOOR, CANDLE
*/
public static List<Block> BLOCKS = List.of(CUBE, STAIRS, SLAB, POST, FENCE, FENCE_GATE, TRAPDOOR, IRON_TRAPDOOR, PRESSURE_PLATE, BUTTON, LEVER, WALL, CARPET, PANE);
@Override
public void onInitializeDataGenerator(FabricDataGenerator data_generator) {
FabricDataGenerator.Pack myPack = data_generator.createPack();
myPack.addProvider(GBlockLoot::new);
myPack.addProvider(GBlockstate::new);
FabricDataGenerator.Pack my_pack = data_generator.createPack();
my_pack.addProvider(GBlockLoot::new);
my_pack.addProvider(GBlockstate::new);
my_pack.addProvider(GLanguage::new);
my_pack.addProvider(GBlockTag::new);
my_pack.addProvider(GRecipe::new);
my_pack.addProvider(GAdvancement::new);
}
}

View File

@@ -1,7 +0,0 @@
package fr.adrien1106.reframed.generator;
import net.minecraft.data.client.MultipartBlockStateSupplier;
public interface MultipartBlockStateProvider {
MultipartBlockStateSupplier getMultipart();
}

View File

@@ -0,0 +1,8 @@
package fr.adrien1106.reframed.generator;
import net.minecraft.data.server.recipe.RecipeExporter;
public interface RecipeSetter {
void setRecipe(RecipeExporter exporter);
}

View File

@@ -0,0 +1,45 @@
package fr.adrien1106.reframed.mixin;
import com.llamalad7.mixinextras.sugar.Local;
import com.llamalad7.mixinextras.sugar.ref.LocalRef;
import net.minecraft.block.Block;
import net.minecraft.block.BlockEntityProvider;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.BlockItem;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtHelper;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import static fr.adrien1106.reframed.block.ReFramedEntity.BLOCKSTATE_KEY;
@Mixin(BlockItem.class)
public class BlockItemMixin {
@Inject(
method = "writeNbtToBlockEntity",
at = @At(
value = "INVOKE_ASSIGN",
target = "Lnet/minecraft/item/BlockItem;getBlockEntityNbt(Lnet/minecraft/item/ItemStack;)Lnet/minecraft/nbt/NbtCompound;",
shift = At.Shift.AFTER
)
)
private static void placeBlockWithOffHandCamo(World world, PlayerEntity player, BlockPos pos, ItemStack stack, CallbackInfoReturnable<Boolean> cir, @Local LocalRef<NbtCompound> compound) {
if (compound.get() != null
|| player.getOffHandStack().isEmpty()
|| !(player.getOffHandStack().getItem() instanceof BlockItem block)
|| block.getBlock() instanceof BlockEntityProvider
|| !Block.isShapeFullCube(block.getBlock().getDefaultState().getCollisionShape(world, pos))
) return;
NbtCompound new_comp = new NbtCompound();
player.getOffHandStack().decrement(1);
new_comp.put(BLOCKSTATE_KEY + 1, NbtHelper.fromBlockState(block.getBlock().getDefaultState()));
compound.set(new_comp);
}
}

View File

@@ -1,46 +0,0 @@
package fr.adrien1106.reframed.mixin;
import com.llamalad7.mixinextras.sugar.Local;
import fr.adrien1106.reframed.block.ReFramedEntity;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.util.function.BooleanBiFunction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes;
import net.minecraft.world.BlockView;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.*;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(Block.class)
public class BlockMixin {
@Redirect(method = "shouldDrawSide", at = @At(value = "INVOKE", target = "Lnet/minecraft/block/BlockState;isOpaque()Z"))
private static boolean isNeighborCamoOpaque(BlockState state, @Local(argsOnly = true) BlockView world, @Local(ordinal = 1, argsOnly = true) BlockPos pos) {
BlockEntity block_entity = world.getBlockEntity(pos);
if (!(block_entity instanceof ReFramedEntity frame_entity)) return state.isOpaque();
return frame_entity.getThemeState().isOpaque();
}
@Redirect(method = "shouldDrawSide", at = @At(value = "INVOKE", target = "Lnet/minecraft/block/BlockState;isSideInvisible(Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/Direction;)Z"))
private static boolean isCamoInvisible(BlockState state, BlockState other_state, Direction direction, @Local(argsOnly = true) BlockView world, @Local(ordinal = 0, argsOnly = true) BlockPos pos, @Local(ordinal = 1, argsOnly = true) BlockPos other_pos) {
if (world.getBlockEntity(other_pos) instanceof ReFramedEntity entity) other_state = entity.getThemeState();
if (world.getBlockEntity(pos) instanceof ReFramedEntity entity) state = entity.getThemeState();
return state.isSideInvisible(other_state, direction);
}
@Inject(method = "shouldDrawSide", at = @At(value = "RETURN", ordinal = 0), cancellable = true)
private static void shouldDrawGlassCamoSide(BlockState state, BlockView world, BlockPos pos, Direction side, BlockPos other_pos, CallbackInfoReturnable<Boolean> cir, @Local(ordinal = 1) BlockState neighbor) {
if (!(world.getBlockEntity(pos) instanceof ReFramedEntity) && !(world.getBlockEntity(other_pos) instanceof ReFramedEntity)) return;
VoxelShape voxel_block = state.getCullingFace(world, pos, side);
if (voxel_block.isEmpty()) {
cir.setReturnValue(true);
return;
}
VoxelShape voxel_neighbor = neighbor.getCullingFace(world, pos, side.getOpposite());
cir.setReturnValue(VoxelShapes.matchesAnywhere(voxel_block, voxel_neighbor, BooleanBiFunction.ONLY_FIRST));
}
}

View File

@@ -1,49 +0,0 @@
package fr.adrien1106.reframed.mixin;
import com.llamalad7.mixinextras.sugar.Local;
import fr.adrien1106.reframed.block.ReFramedEntity;
import net.fabricmc.fabric.impl.client.indigo.renderer.render.BlockRenderInfo;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.client.MinecraftClient;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
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.Inject;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(BlockRenderInfo.class)
public abstract class BlockRenderInfoMixin {
@Shadow public BlockPos blockPos;
@Shadow public BlockState blockState;
@Shadow public BlockRenderView blockView;
@Shadow @Final private BlockPos.Mutable searchPos;
@ModifyArg(method = "prepareForBlock",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/RenderLayers;" +
"getBlockLayer(Lnet/minecraft/block/BlockState;)Lnet/minecraft/client/render/RenderLayer;"))
public BlockState prepareCamoLayer(BlockState state, @Local(argsOnly = true) BlockPos pos) {
BlockEntity block_entity = MinecraftClient.getInstance().world.getBlockEntity(pos);
if (!(block_entity instanceof ReFramedEntity frame_entity)) return state;
return frame_entity.getThemeState();
}
@Inject(method = "shouldDrawFace",
at = @At(value = "INVOKE_ASSIGN", target = "Lnet/minecraft/util/math/Direction;getId()I"),
cancellable = true)
private void shouldDrawCamoFace(Direction face, CallbackInfoReturnable<Boolean> cir) {
BlockEntity block_entity = MinecraftClient.getInstance().world.getBlockEntity(blockPos);
if (!(block_entity instanceof ReFramedEntity)) return;
cir.setReturnValue(Block.shouldDrawSide(blockState, blockView, blockPos, face, searchPos.set(blockPos, face)));
}
}

View File

@@ -0,0 +1,71 @@
package fr.adrien1106.reframed.mixin;
import net.fabricmc.loader.api.FabricLoader;
import org.objectweb.asm.tree.ClassNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
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<String> COMPAT_MOD = List.of("athena", "indium", "sodium");
private static final Map<String, Supplier<Boolean>> 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))
);
@Override
public void onLoad(String mixin_package) {
}
@Override
public String getRefMapperConfig() {
return null;
}
@Override
public boolean shouldApplyMixin(String target_class, String mixin_class) {
return CONDITIONS.getOrDefault(mixin_class, () -> true).get();
}
@Override
public void acceptTargets(Set<String> mine, Set<String> others) {
}
@Override
public List<String> getMixins() {
return null;
}
@Override
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) {
String mixin_class_name = mixin_class.substring(mixin_class.lastIndexOf('.') + 1);
COMPAT_MOD.forEach(mod -> {
if (mixin_class_name.toLowerCase().startsWith(mod))
LOGGER.info("Loaded compatibility mixin class for mod \"" + mod + "\" (class: " + target_class + ")");
});
}
}

View File

@@ -0,0 +1,81 @@
package fr.adrien1106.reframed.mixin.compat;
import earth.terrarium.athena.api.client.fabric.AthenaBakedModel;
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.util.blocks.ThemeableBlockEntity;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import net.fabricmc.fabric.api.renderer.v1.Renderer;
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
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.world.BlockRenderView;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import java.util.*;
@Mixin(AthenaBakedModel.class)
public abstract class AthenaBakedModelMixin implements DynamicBakedModel, BakedModel {
@Shadow(remap = false) @Final private AthenaBlockModel model;
@Shadow(remap = false) @Final private Int2ObjectMap<Sprite> textures;
/**
* 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 pos - the block position
* @return - the rebakedmodel containing the computed quads
*/
@Override
public BakedModel computeQuads(BlockRenderView level, BlockState state, BlockPos pos, int theme_index) {
Map<Direction, List<BakedQuad>> face_quads = new HashMap<>();
Renderer r = ReFramedClient.HELPER.getFabricRenderer();
QuadEmitter emitter = r.meshBuilder().getEmitter();
WrappedGetter getter = new WrappedGetter(level);
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)
).forEach(sprite -> face_quads.computeIfPresent(direction, (d, quads) -> {
Sprite texture = textures.get(sprite.sprite());
if (texture == null) return quads;
emitter.square(direction, sprite.left(), sprite.bottom(), sprite.right(), sprite.top(), sprite.depth());
int flag = MutableQuadView.BAKE_LOCK_UV;
switch (sprite.rotation()) {
case CLOCKWISE_90 -> flag |= MutableQuadView.BAKE_ROTATE_90;
case CLOCKWISE_180 -> flag |= MutableQuadView.BAKE_ROTATE_180;
case COUNTERCLOCKWISE_90 -> flag |= MutableQuadView.BAKE_ROTATE_270;
}
emitter.spriteBake(texture, flag);
emitter.color(-1, -1, -1, -1);
quads.add(emitter.toBakedQuad(texture));
return quads;
}));
});
return new RebakedAthenaModel(face_quads);
}
}

View File

@@ -0,0 +1,30 @@
package fr.adrien1106.reframed.mixin.compat;
import earth.terrarium.athena.api.client.utils.AppearanceAndTintGetter;
import earth.terrarium.athena.api.client.utils.CtmUtils;
import earth.terrarium.athena.impl.client.models.ConnectedBlockModel;
import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(ConnectedBlockModel.class)
public class AthenaConnectedBlockModelMixin {
@Redirect(method = "getQuads",
at = @At(value = "INVOKE", target = "Learth/terrarium/athena/api/client/utils/CtmUtils;" +
"checkRelative(Learth/terrarium/athena/api/client/utils/AppearanceAndTintGetter;" +
"Lnet/minecraft/block/BlockState;" +
"Lnet/minecraft/util/math/BlockPos;" +
"Lnet/minecraft/util/math/Direction;)Z"))
private boolean checkForCull(AppearanceAndTintGetter level, BlockState state, BlockPos pos, Direction direction) {
// Always get all the textures unless its another block then use default behaviour
if (level.getBlockEntity(pos) instanceof ThemeableBlockEntity
|| level.getBlockEntity(pos.offset(direction)) instanceof ThemeableBlockEntity)
return false;
return CtmUtils.checkRelative(level, state, pos, direction);
}
}

View File

@@ -0,0 +1,30 @@
package fr.adrien1106.reframed.mixin.compat;
import com.llamalad7.mixinextras.sugar.Local;
import earth.terrarium.athena.api.client.fabric.WrappedGetter;
import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
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(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;"))
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)
return framed_entity.getThemes()
.stream()
.filter(state -> reference_state.isOf(state.getBlock()))
.findAny()
.orElse(framed_entity.getTheme(1));
return world.getBlockState(pos);
}
}

View File

@@ -0,0 +1,29 @@
package fr.adrien1106.reframed.mixin.compat;
import com.llamalad7.mixinextras.sugar.Local;
import fr.adrien1106.reframed.util.blocks.BlockHelper;
import fr.adrien1106.reframed.util.mixin.IBlockRenderInfoMixin;
import link.infra.indium.renderer.mesh.MutableQuadViewImpl;
import link.infra.indium.renderer.render.AbstractBlockRenderContext;
import link.infra.indium.renderer.render.BlockRenderInfo;
import net.minecraft.util.math.Direction;
import org.jetbrains.annotations.Nullable;
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;
@Mixin(AbstractBlockRenderContext.class)
public abstract class IndiumAbstractBlockRenderContextMixin {
@Shadow(remap = false) protected BlockRenderInfo blockInfo;
@Shadow public abstract boolean isFaceCulled(@Nullable Direction face);
@Redirect(method = "renderQuad", at = @At(value = "INVOKE", target = "Llink/infra/indium/renderer/render/AbstractBlockRenderContext;isFaceCulled(Lnet/minecraft/util/math/Direction;)Z"))
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());
}
}

View File

@@ -0,0 +1,48 @@
package fr.adrien1106.reframed.mixin.compat;
import fr.adrien1106.reframed.util.blocks.BlockHelper;
import fr.adrien1106.reframed.util.mixin.IBlockRenderInfoMixin;
import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
import link.infra.indium.renderer.render.BlockRenderInfo;
import link.infra.indium.renderer.render.TerrainBlockRenderInfo;
import me.jellysquid.mods.sodium.client.render.chunk.compile.pipeline.BlockOcclusionCache;
import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.world.BlockView;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(TerrainBlockRenderInfo.class)
public abstract class IndiumTerrainBlockRenderInfoMixin extends BlockRenderInfo implements IBlockRenderInfoMixin {
@Unique private int theme_index = 0;
@Redirect(
method = "shouldDrawFaceInner",
at = @At(
value = "INVOKE",
target = "Lme/jellysquid/mods/sodium/client/render/chunk/compile/pipeline/BlockOcclusionCache;shouldDrawSide(Lnet/minecraft/block/BlockState;Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/math/Direction;)Z"
)
)
private boolean shouldDrawCamoSide(BlockOcclusionCache instance, BlockState state, BlockView view, BlockPos pos, Direction face) {
BlockPos other_pos = pos.offset(face);
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);
}
@Override
public void prepareForBlock(BlockState blockState, BlockPos blockPos, long seed, boolean modelAo, int theme_index) {
this.theme_index = theme_index;
prepareForBlock(blockState, blockPos, seed, modelAo);
}
@Override
public int getThemeIndex() {
return theme_index;
}
}

View File

@@ -0,0 +1,45 @@
package fr.adrien1106.reframed.mixin.compat;
import fr.adrien1106.reframed.client.model.MultiRetexturableModel;
import fr.adrien1106.reframed.util.blocks.BlockHelper;
import fr.adrien1106.reframed.util.mixin.IBlockRenderInfoMixin;
import fr.adrien1106.reframed.util.mixin.IMultipartBakedModelMixin;
import link.infra.indium.renderer.render.AbstractBlockRenderContext;
import link.infra.indium.renderer.render.TerrainRenderContext;
import me.jellysquid.mods.sodium.client.render.chunk.compile.pipeline.BlockRenderContext;
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
import net.minecraft.client.render.model.BakedModel;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.List;
@Mixin(TerrainRenderContext.class)
public abstract class IndiumTerrainRenderContextMixin extends AbstractBlockRenderContext {
@Inject(
method = "tessellateBlock",
at = @At(
value = "INVOKE",
target = "Llink/infra/indium/renderer/aocalc/AoCalculator;clear()V"
), remap = false,
cancellable = true)
private void renderMultipleModels(BlockRenderContext ctx, CallbackInfo ci) {
if (!(ctx.model() instanceof IMultipartBakedModelMixin wrapped)
|| !(wrapped.getModel(ctx.state()) instanceof MultiRetexturableModel retexturing_model)) return;
List<ForwardingBakedModel> models = retexturing_model.models();
BlockHelper.computeInnerCull(ctx.state(), models);
int i = 0;
for (BakedModel model : models) {
i++;
aoCalc.clear();
((IBlockRenderInfoMixin) blockInfo).prepareForBlock(ctx.state(), ctx.pos(), ctx.seed(), model.useAmbientOcclusion(), i);
model.emitBlockQuads(blockInfo.blockView, blockInfo.blockState, blockInfo.blockPos, blockInfo.randomSupplier, this);
}
ci.cancel();
}
}

View File

@@ -0,0 +1,30 @@
package fr.adrien1106.reframed.mixin.compat;
import com.llamalad7.mixinextras.sugar.Local;
import fr.adrien1106.reframed.util.blocks.BlockHelper;
import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
import me.jellysquid.mods.sodium.client.render.chunk.compile.pipeline.BlockOcclusionCache;
import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.world.BlockView;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(BlockOcclusionCache.class)
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;",
shift = At.Shift.AFTER
), cancellable = true)
private void shouldDrawFrameNeighborSide(BlockState self_state, BlockView view, BlockPos self_pos, Direction face, CallbackInfoReturnable<Boolean> 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));
}
}

View File

@@ -1,6 +1,7 @@
package fr.adrien1106.reframed.mixin.particles;
import fr.adrien1106.reframed.util.ThemeableBlockEntity;
import fr.adrien1106.reframed.block.ReFramedBlock;
import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
import net.minecraft.block.BlockState;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.particle.BlockDustParticle;
@@ -20,8 +21,11 @@ public class MixinBlockDustParticle {
)
void modifyParticleSprite(ClientWorld clientWorld, double d, double e, double f, double g, double h, double i, BlockState state, BlockPos pos, CallbackInfo ci) {
AccessorParticle a = (AccessorParticle) this;
if(a.getRandom().nextBoolean() && clientWorld.getBlockEntity(pos) instanceof ThemeableBlockEntity themeable) {
BlockState theme = themeable.getThemeState();
if(a.getRandom().nextBoolean()
&& clientWorld.getBlockEntity(pos) instanceof ThemeableBlockEntity themeable
&& state.getBlock() instanceof ReFramedBlock block
) {
BlockState theme = themeable.getTheme(block.getTopThemeIndex(state));
if(theme == null || theme.isAir()) return;
Sprite replacement = MinecraftClient.getInstance().getBlockRenderManager().getModels().getModelParticleSprite(theme);

View File

@@ -1,31 +1,31 @@
package fr.adrien1106.reframed.mixin.particles;
import fr.adrien1106.reframed.util.ThemeableBlockEntity;
import com.llamalad7.mixinextras.sugar.Local;
import fr.adrien1106.reframed.block.ReFramedBlock;
import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
import net.minecraft.block.BlockState;
import net.minecraft.entity.Entity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
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.ModifyArg;
@Mixin(Entity.class)
public abstract class MixinEntity {
@Shadow @Deprecated public abstract BlockPos getLandingPos(); //TODO, somewhat expensive method
@ModifyArg(
method = "spawnSprintingParticles",
at = @At(value = "INVOKE", target = "Lnet/minecraft/particle/BlockStateParticleEffect;<init>(Lnet/minecraft/particle/ParticleType;Lnet/minecraft/block/BlockState;)V")
)
private BlockState modifyParticleState(BlockState origState) {
private BlockState modifyParticleState(BlockState state, @Local(ordinal = 0) BlockPos landing_pos) {
World world = ((Entity) (Object) this).getWorld();
if(world.getBlockEntity(getLandingPos()) instanceof ThemeableBlockEntity themeable) {
BlockState theme = themeable.getThemeState();
if(!theme.isAir()) return theme;
if(world.getBlockEntity(landing_pos) instanceof ThemeableBlockEntity themeable
&& state.getBlock() instanceof ReFramedBlock block) {
BlockState theme = themeable.getTheme(block.getTopThemeIndex(state));
if(!theme.isAir()) return theme;
}
return origState;
return state;
}
}

View File

@@ -1,39 +1,33 @@
package fr.adrien1106.reframed.mixin.particles;
import fr.adrien1106.reframed.util.ThemeableBlockEntity;
import com.llamalad7.mixinextras.sugar.Local;
import fr.adrien1106.reframed.block.ReFramedBlock;
import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
import net.minecraft.block.BlockState;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(LivingEntity.class)
public class MixinLivingEntity {
@Unique private BlockPos lastFallCheckPos;
@Inject(method = "fall", at = @At("HEAD"))
private void onFall(double d, boolean bl, BlockState blockState, BlockPos blockPos, CallbackInfo ci) {
lastFallCheckPos = blockPos;
}
@ModifyArg(
method = "fall",
at = @At(value = "INVOKE", target = "Lnet/minecraft/particle/BlockStateParticleEffect;<init>(Lnet/minecraft/particle/ParticleType;Lnet/minecraft/block/BlockState;)V")
)
private BlockState modifyParticleState(BlockState origState) {
private BlockState modifyParticleState(BlockState state, @Local(ordinal = 0, argsOnly = true) BlockPos land_pos) {
World world = ((Entity) (Object) this).getWorld();
if(lastFallCheckPos != null && world.getBlockEntity(lastFallCheckPos) instanceof ThemeableBlockEntity themeable) {
BlockState theme = themeable.getThemeState();
if(!theme.isAir()) return theme;
if(world.getBlockEntity(land_pos) instanceof ThemeableBlockEntity themeable
&& state.getBlock() instanceof ReFramedBlock block) {
BlockState theme = themeable.getTheme(block.getTopThemeIndex(state));
if(!theme.isAir()) return theme;
}
return origState;
return state;
}
}

View File

@@ -0,0 +1,36 @@
package fr.adrien1106.reframed.mixin.render;
import com.llamalad7.mixinextras.sugar.Local;
import fr.adrien1106.reframed.util.blocks.BlockHelper;
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;
import net.fabricmc.fabric.impl.client.indigo.renderer.render.BlockRenderInfo;
import net.minecraft.util.math.Direction;
import org.jetbrains.annotations.Nullable;
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;
@Mixin(AbstractBlockRenderContext.class)
public abstract class AbstractBlockRenderContextMixin {
@Shadow public abstract boolean isFaceCulled(@Nullable Direction face);
@Shadow(remap = false) @Final protected BlockRenderInfo blockInfo;
@Redirect(
method = "renderQuad",
at = @At(
value = "INVOKE",
target = "Lnet/fabricmc/fabric/impl/client/indigo/renderer/render/AbstractBlockRenderContext;isFaceCulled(Lnet/minecraft/util/math/Direction;)Z"
)
)
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());
}
}

View File

@@ -0,0 +1,54 @@
package fr.adrien1106.reframed.mixin.render;
import com.llamalad7.mixinextras.sugar.Local;
import fr.adrien1106.reframed.util.blocks.BlockHelper;
import fr.adrien1106.reframed.util.mixin.IBlockRenderInfoMixin;
import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
import net.fabricmc.fabric.impl.client.indigo.renderer.render.BlockRenderInfo;
import net.minecraft.block.BlockState;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.world.BlockRenderView;
import net.minecraft.world.BlockView;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(BlockRenderInfo.class)
public abstract class BlockRenderInfoMixin implements IBlockRenderInfoMixin {
@Shadow public abstract void prepareForBlock(BlockState blockState, BlockPos blockPos, boolean modelAo);
@Shadow public BlockRenderView blockView;
@Unique private int theme_index = 0;
@ModifyArg(method = "prepareForBlock",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/RenderLayers;" +
"getBlockLayer(Lnet/minecraft/block/BlockState;)Lnet/minecraft/client/render/RenderLayer;"))
public BlockState prepareCamoLayer(BlockState state, @Local(argsOnly = true) BlockPos pos) {
BlockEntity block_entity = blockView.getBlockEntity(pos);
if (!(block_entity instanceof ThemeableBlockEntity frame_entity)) return state;
return frame_entity.getTheme(theme_index);
}
@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);
}
@Override
@Unique
public void prepareForBlock(BlockState state, BlockPos pos, boolean ao, int theme_index) {
this.theme_index = theme_index;
prepareForBlock(state, pos, ao);
}
@Override
public int getThemeIndex() {
return theme_index;
}
}

View File

@@ -0,0 +1,25 @@
package fr.adrien1106.reframed.mixin.render;
import fr.adrien1106.reframed.util.mixin.IMultipartBakedModelMixin;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.MultipartBakedModel;
import org.apache.commons.lang3.tuple.Pair;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
@Mixin(MultipartBakedModel.class)
public class MultipartBakedModelMixin implements IMultipartBakedModelMixin {
@Shadow @Final private List<Pair<Predicate<BlockState>, BakedModel>> components;
@Override
public BakedModel getModel(BlockState state) {
return components.stream().map(pair -> pair.getLeft().test(state) ? pair.getRight(): null).filter(Objects::nonNull).findAny().orElse(null);
}
}

View File

@@ -0,0 +1,44 @@
package fr.adrien1106.reframed.mixin.render;
import fr.adrien1106.reframed.client.model.MultiRetexturableModel;
import fr.adrien1106.reframed.util.blocks.BlockHelper;
import fr.adrien1106.reframed.util.mixin.IBlockRenderInfoMixin;
import fr.adrien1106.reframed.util.mixin.IMultipartBakedModelMixin;
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
import net.fabricmc.fabric.impl.client.indigo.renderer.render.AbstractBlockRenderContext;
import net.fabricmc.fabric.impl.client.indigo.renderer.render.TerrainRenderContext;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.util.math.BlockPos;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.List;
@Mixin(TerrainRenderContext.class)
public abstract class TerrainRenderContextMixin extends AbstractBlockRenderContext {
@Inject(method = "tessellateBlock", at = @At(
value = "INVOKE",
target = "Lnet/fabricmc/fabric/impl/client/indigo/renderer/aocalc/AoCalculator;clear()V"
), remap = false,
cancellable = true)
private void renderMultipleModels(BlockState state, BlockPos pos, BakedModel wrapper, MatrixStack matrixStack, CallbackInfo ci) {
if (!(wrapper instanceof IMultipartBakedModelMixin wrapped)
|| !(wrapped.getModel(state) instanceof MultiRetexturableModel retexturing_model)) return;
List<ForwardingBakedModel> models = retexturing_model.models();
BlockHelper.computeInnerCull(state, models);
int i = 0;
for (BakedModel model : models) {
i++;
aoCalc.clear();
((IBlockRenderInfoMixin) blockInfo).prepareForBlock(state, pos, model.useAmbientOcclusion(), i);
model.emitBlockQuads(blockInfo.blockView, blockInfo.blockState, blockInfo.blockPos, blockInfo.randomSupplier, this);
}
ci.cancel();
}
}

View File

@@ -0,0 +1,35 @@
package fr.adrien1106.reframed.mixin.render;
import fr.adrien1106.reframed.block.ReFramedDoubleBlock;
import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.WorldRenderer;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.BlockView;
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;
@Mixin(WorldRenderer.class)
public class WorldRendererMixin {
@Shadow @Final private MinecraftClient client;
@Redirect(method = "drawBlockOutline",
at = @At(value = "INVOKE", target = "Lnet/minecraft/block/BlockState;" +
"getOutlineShape(" +
"Lnet/minecraft/world/BlockView;" +
"Lnet/minecraft/util/math/BlockPos;" +
"Lnet/minecraft/block/ShapeContext;" +
")Lnet/minecraft/util/shape/VoxelShape;"))
private VoxelShape getRenderOutline(BlockState state, BlockView world, BlockPos pos, ShapeContext shape_context) {
if (state.getBlock() instanceof ReFramedDoubleBlock double_frame_block) // cast is already checked in render
return double_frame_block.getRenderOutline(state, (BlockHitResult) client.crosshairTarget);
return state.getOutlineShape(world, pos, shape_context);
}
}

View File

@@ -0,0 +1,48 @@
package fr.adrien1106.reframed.mixin.sound;
import com.llamalad7.mixinextras.sugar.Local;
import fr.adrien1106.reframed.block.ReFramedBlock;
import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.item.BlockItem;
import net.minecraft.sound.BlockSoundGroup;
import net.minecraft.sound.SoundEvent;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(BlockItem.class)
public class BlockItemMixin {
@Redirect(
method = "place(Lnet/minecraft/item/ItemPlacementContext;)Lnet/minecraft/util/ActionResult;",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/block/BlockState;getSoundGroup()Lnet/minecraft/sound/BlockSoundGroup;"
)
)
public BlockSoundGroup getCamoPlaceSound(BlockState state, @Local World world, @Local BlockPos pos) {
if (state.getBlock() instanceof ReFramedBlock frame_block
&& world.getBlockEntity(pos) instanceof ThemeableBlockEntity frame_entity
) {
BlockState camo_state = frame_entity.getTheme(frame_block.getTopThemeIndex(state));
state = camo_state.getBlock() != Blocks.AIR ? camo_state : state;
}
return state.getSoundGroup();
}
@Redirect(
method = "place(Lnet/minecraft/item/ItemPlacementContext;)Lnet/minecraft/util/ActionResult;",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/item/BlockItem;getPlaceSound(Lnet/minecraft/block/BlockState;)Lnet/minecraft/sound/SoundEvent;"
)
)
private SoundEvent getCamoSoundEvent(BlockItem item, BlockState state, @Local BlockSoundGroup group) {
// I don't know why it wasn't doing that by default
return group.getPlaceSound();
}
}

View File

@@ -0,0 +1,38 @@
package fr.adrien1106.reframed.mixin.sound;
import com.llamalad7.mixinextras.sugar.Local;
import fr.adrien1106.reframed.block.ReFramedBlock;
import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.entity.Entity;
import net.minecraft.sound.BlockSoundGroup;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
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;
@Mixin(Entity.class)
public abstract class EntityMixin {
@Shadow public abstract World getWorld();
@Redirect(
method = "playStepSound",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/block/BlockState;getSoundGroup()Lnet/minecraft/sound/BlockSoundGroup;"
)
)
private BlockSoundGroup playStepCamoSound(BlockState state, @Local(argsOnly = true) BlockPos pos) {
if (state.getBlock() instanceof ReFramedBlock frame_block
&& getWorld().getBlockEntity(pos) instanceof ThemeableBlockEntity frame_entity
) {
BlockState camo_state = frame_entity.getTheme(frame_block.getTopThemeIndex(state));
state = camo_state.getBlock() != Blocks.AIR ? camo_state : state;
}
return state.getSoundGroup();
}
}

View File

@@ -0,0 +1,34 @@
package fr.adrien1106.reframed.mixin.sound;
import fr.adrien1106.reframed.block.ReFramedBlock;
import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.entity.LivingEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(LivingEntity.class)
public class LivingEntityMixin {
@Redirect(
method = "playBlockFallSound",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/World;getBlockState(Lnet/minecraft/util/math/BlockPos;)Lnet/minecraft/block/BlockState;"
)
)
private BlockState playCamoFallSound(World world, BlockPos pos) {
BlockState state = world.getBlockState(pos);
if (state.getBlock() instanceof ReFramedBlock frame_block
&& world.getBlockEntity(pos) instanceof ThemeableBlockEntity frame_entity
) {
BlockState camo_state = frame_entity.getTheme(frame_block.getTopThemeIndex(state));
state = camo_state.getBlock() != Blocks.AIR ? camo_state : state;
}
return state;
}
}

View File

@@ -0,0 +1,39 @@
package fr.adrien1106.reframed.mixin.sound;
import com.llamalad7.mixinextras.sugar.Local;
import fr.adrien1106.reframed.block.ReFramedBlock;
import fr.adrien1106.reframed.util.blocks.ThemeableBlockEntity;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.client.render.WorldRenderer;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.sound.BlockSoundGroup;
import net.minecraft.util.math.BlockPos;
import org.jetbrains.annotations.Nullable;
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;
@Mixin(WorldRenderer.class)
public class WorldRendererMixin {
@Shadow @Nullable private ClientWorld world;
@Redirect(
method = "processWorldEvent",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/block/BlockState;getSoundGroup()Lnet/minecraft/sound/BlockSoundGroup;"
)
)
private BlockSoundGroup getCamoBreakSound(BlockState state, @Local(argsOnly = true) BlockPos pos) {
if (state.getBlock() instanceof ReFramedBlock frame_block
&& world.getBlockEntity(pos) instanceof ThemeableBlockEntity frame_entity
) {
BlockState camo_state = frame_entity.getTheme(frame_block.getTopThemeIndex(state));
state = camo_state.getBlock() != Blocks.AIR ? camo_state : state;
}
return state.getSoundGroup();
}
}

View File

@@ -1,182 +0,0 @@
package fr.adrien1106.reframed.util;
import fr.adrien1106.reframed.block.ReFramedEntity;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockEntityProvider;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.ShapeContext;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.BlockItem;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.item.ItemStack;
import net.minecraft.item.ItemUsageContext;
import net.minecraft.item.Items;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.sound.BlockSoundGroup;
import net.minecraft.sound.SoundCategory;
import net.minecraft.sound.SoundEvents;
import net.minecraft.state.StateManager;
import net.minecraft.state.property.BooleanProperty;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.ItemScatterer;
import net.minecraft.util.collection.DefaultedList;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes;
import net.minecraft.world.BlockView;
import net.minecraft.world.GameRules;
import net.minecraft.world.World;
import org.jetbrains.annotations.Nullable;
//For an example of how to use this class, have a look at TemplateBlock.
//Basically there are several methods that would like to modify the return value of something.
public class ReFramedInteractionUtil {
public static final BooleanProperty LIGHT = BooleanProperty.of("frame_light");
public static StateManager.Builder<Block, BlockState> appendProperties(StateManager.Builder<Block, BlockState> builder) {
return builder.add(LIGHT);
}
//Use this to obtain a Block.Settings that'll make your Template act like the ones in the mod.
//(To complete the look, don't forget to tag your blocks with mineable/axe.)
private static final AbstractBlock.ContextPredicate NOPE = (blah, blahdey, blahh) -> false;
public static AbstractBlock.Settings configureSettings(AbstractBlock.Settings s) {
return s.luminance(ReFramedInteractionUtil::luminance).nonOpaque().sounds(BlockSoundGroup.WOOD).hardness(0.2f).suffocates(NOPE).blockVision(NOPE);
}
//And if you don't have a Block.Settings to copy off of.
public static AbstractBlock.Settings makeSettings() {
return configureSettings(AbstractBlock.Settings.create());
}
public static BlockState setDefaultStates(BlockState in) {
if(in.contains(LIGHT)) in = in.with(LIGHT, false);
return in;
}
public static @Nullable BlockState modifyPlacementState(@Nullable BlockState in, ItemPlacementContext ctx) {
return ReFramedEntity.weirdNbtLightLevelStuff(in, ctx.getStack());
}
public static ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
if(!(world.getBlockEntity(pos) instanceof ReFramedEntity be)) return ActionResult.PASS;
if(!player.canModifyBlocks() || !world.canPlayerModifyAt(player, pos)) return ActionResult.PASS;
ItemStack held = player.getStackInHand(hand);
ReframedInteractible ext = state.getBlock() instanceof ReframedInteractible e ? e : ReframedInteractible.Default.INSTANCE;
//Glowstone
if(state.contains(LIGHT) && held.getItem() == Items.GLOWSTONE_DUST && !state.get(LIGHT) && !be.hasSpentGlowstoneDust()) {
world.setBlockState(pos, state.with(LIGHT, true));
be.spentGlowstoneDust();
if(!player.isCreative()) held.decrement(1);
world.playSound(player, pos, SoundEvents.BLOCK_GLASS_HIT, SoundCategory.BLOCKS, 1f, 1f);
return ActionResult.SUCCESS;
}
//Redstone
if(held.getItem() == Blocks.REDSTONE_TORCH.asItem() &&
!be.emitsRedstone() &&
!be.hasSpentRedstoneTorch() &&
ext.canAddRedstoneEmission(state, world, pos)
) {
be.setEmitsRedstone(true);
be.spentRedstoneTorch();
if(!player.isCreative()) held.decrement(1);
world.playSound(player, pos, SoundEvents.BLOCK_LEVER_CLICK, SoundCategory.BLOCKS, 1f, 1f);
return ActionResult.SUCCESS;
}
//Popped chorus fruit
if(held.getItem() == Items.POPPED_CHORUS_FRUIT &&
be.isSolid() &&
!be.hasSpentPoppedChorus() &&
ext.canRemoveCollision(state, world, pos)
) {
be.setSolidity(false);
be.spentPoppedChorus();
if(!player.isCreative()) held.decrement(1);
world.playSound(player, pos, SoundEvents.ITEM_CHORUS_FRUIT_TELEPORT, SoundCategory.BLOCKS, 1f, 1f);
return ActionResult.SUCCESS;
}
//Changing the theme
if(held.getItem() instanceof BlockItem bi && be.getThemeState().getBlock() == Blocks.AIR) {
Block block = bi.getBlock();
ItemPlacementContext ctx = new ItemPlacementContext(new ItemUsageContext(player, hand, hit));
BlockState placementState = block.getPlacementState(ctx);
if(placementState != null && Block.isShapeFullCube(placementState.getCollisionShape(world, pos)) && !(block instanceof BlockEntityProvider)) {
if(!world.isClient) be.setRenderedState(placementState);
world.setBlockState(pos, state.with(LIGHT, be.hasSpentGlowstoneDust() || (placementState.getLuminance() != 0)));
be.setEmitsRedstone(be.hasSpentRedstoneTorch() || placementState.getWeakRedstonePower(world, pos, Direction.NORTH) != 0);
if(!player.isCreative()) held.decrement(1);
world.playSound(player, pos, placementState.getSoundGroup().getPlaceSound(), SoundCategory.BLOCKS, 1f, 1.1f);
return ActionResult.SUCCESS;
}
}
return ActionResult.PASS;
}
//Maybe an odd spot to put this logic but it's consistent w/ vanilla chests, barrels, etc
public static void onStateReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) {
if(!state.isOf(newState.getBlock()) &&
world.getBlockEntity(pos) instanceof ReFramedEntity frame &&
world.getGameRules().getBoolean(GameRules.DO_TILE_DROPS)
) {
DefaultedList<ItemStack> drops = DefaultedList.of();
//TODO: remember the specific ItemStack
Block theme = frame.getThemeState().getBlock();
if(theme != Blocks.AIR) drops.add(new ItemStack(theme));
if(frame.hasSpentRedstoneTorch()) drops.add(new ItemStack(Items.REDSTONE_TORCH));
if(frame.hasSpentGlowstoneDust()) drops.add(new ItemStack(Items.GLOWSTONE_DUST));
if(frame.hasSpentPoppedChorus()) drops.add(new ItemStack(Items.POPPED_CHORUS_FRUIT));
ItemScatterer.spawn(world, pos, drops);
}
}
public static void onPlaced(World world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) {
//Load the BlockEntityTag clientside, which fixes the template briefly showing its default state when placing it.
//I'm surprised this doesn't happen by default; the BlockEntityTag stuff is only done serverside.
if(world.isClient && world.getBlockEntity(pos) instanceof ReFramedEntity be) {
NbtCompound tag = BlockItem.getBlockEntityNbt(stack);
if(tag != null) be.readNbt(tag);
}
}
//Returns "null" to signal "no opinion". Imagine it like an InteractionResult.PASS.
public static @Nullable VoxelShape getCollisionShape(BlockState state, BlockView view, BlockPos pos, ShapeContext ctx) {
return view.getBlockEntity(pos) instanceof ReFramedEntity be && !be.isSolid() ? VoxelShapes.empty() : null;
}
public static boolean emitsRedstonePower(BlockState state) {
//return state.contains(REDSTONE) ? state.get(REDSTONE) : false;
return false; //TODO, not available after punting this to BlockEntity. Yarn makes this method sound more important than it is, it's just for dust redirection.
}
public static int getWeakRedstonePower(BlockState state, BlockView view, BlockPos pos, Direction dir) {
return view.getBlockEntity(pos) instanceof ReFramedEntity be && be.emitsRedstone() ? 15 : 0;
}
public static int getStrongRedstonePower(BlockState state, BlockView view, BlockPos pos, Direction dir) {
return view.getBlockEntity(pos) instanceof ReFramedEntity be && be.emitsRedstone() ? 15 : 0;
}
public static int luminance(BlockState state) {
return state.contains(LIGHT) && state.get(LIGHT) ? 15 : 0;
}
}

View File

@@ -1,10 +0,0 @@
package fr.adrien1106.reframed.util;
import net.fabricmc.fabric.api.rendering.data.v1.RenderAttachmentBlockEntity;
import net.minecraft.block.BlockState;
public interface ThemeableBlockEntity extends RenderAttachmentBlockEntity {
default BlockState getThemeState() {
return (BlockState) getRenderAttachmentData();
}
}

View File

@@ -5,68 +5,201 @@ import net.minecraft.util.math.Direction;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import static net.minecraft.util.shape.VoxelShapes.*;
public class VoxelHelper {
/* ---------------------------------------- Methods for VoxelListBuilder ---------------------------------------- */
public static VoxelShape rotateX(VoxelShape shape) {
return rotateClockwise(shape, Direction.Axis.X);
}
public static VoxelShape rotateY(VoxelShape shape) {
return rotateClockwise(shape, Direction.Axis.Y);
}
public static VoxelShape rotateZ(VoxelShape shape) {
return rotateClockwise(shape, Direction.Axis.Z);
}
public static VoxelShape rotateCX(VoxelShape shape) {
return rotateCounterClockwise(shape, Direction.Axis.X);
}
public static VoxelShape rotateCY(VoxelShape shape) {
return rotateCounterClockwise(shape, Direction.Axis.Y);
}
public static VoxelShape rotateCZ(VoxelShape shape) {
return rotateCounterClockwise(shape, Direction.Axis.Z);
}
public static VoxelShape mirrorX(VoxelShape shape) {
return mirror(shape, Direction.Axis.X);
}
public static VoxelShape mirrorY(VoxelShape shape) {
return mirror(shape, Direction.Axis.Y);
}
public static VoxelShape mirrorZ(VoxelShape shape) {
return mirror(shape, Direction.Axis.Z);
}
public static VoxelShape rotateClockwise(VoxelShape shape, Direction.Axis axis) {
VoxelShape[] buffer = new VoxelShape[]{ shape, VoxelShapes.empty() };
switch (axis) {
case Y:
buffer[0].forEachBox((minX, minY, minZ, maxX, maxY, maxZ) -> buffer[1] = VoxelShapes.combineAndSimplify( buffer[1], VoxelShapes.cuboid( 1 - maxZ, minY, minX, 1 - minZ, maxY, maxX), BooleanBiFunction.OR));
break;
case X:
buffer[0].forEachBox((minX, minY, minZ, maxX, maxY, maxZ) -> buffer[1] = VoxelShapes.combineAndSimplify( buffer[1], VoxelShapes.cuboid( minX, 1 - maxZ, minY, maxX, 1 - minZ, maxY), BooleanBiFunction.OR));
break;
case Z:
buffer[0].forEachBox((minX, minY, minZ, maxX, maxY, maxZ) -> buffer[1] = VoxelShapes.combineAndSimplify( buffer[1], VoxelShapes.cuboid( 1 - maxY, minX, minZ, 1 - minY, maxX, maxZ), BooleanBiFunction.OR));
break;
}
return buffer[1];
AtomicReference<VoxelShape> new_shape = new AtomicReference<>(empty());
shape.forEachBox((minX, minY, minZ, maxX, maxY, maxZ) ->
new_shape.getAndUpdate(s ->
combineAndSimplify(
s,
switch (axis) {
case Y -> cuboid(1 - maxZ, minY, minX, 1 - minZ, maxY, maxX);
case X -> cuboid(minX, 1 - maxZ, minY, maxX, 1 - minZ, maxY);
case Z -> cuboid(1 - maxY, minX, minZ, 1 - minY, maxX, maxZ);
},
BooleanBiFunction.OR
)
)
);
return new_shape.get();
}
public static VoxelShape rotateCounterClockwise(VoxelShape shape, Direction.Axis axis) {
VoxelShape[] buffer = new VoxelShape[]{ shape, VoxelShapes.empty() };
switch (axis) {
case Y:
buffer[0].forEachBox((minX, minY, minZ, maxX, maxY, maxZ) -> buffer[1] = VoxelShapes.combineAndSimplify( buffer[1], VoxelShapes.cuboid( minZ, minY, 1 - maxX, maxZ, maxY, 1 - minX), BooleanBiFunction.OR));
break;
case X:
buffer[0].forEachBox((minX, minY, minZ, maxX, maxY, maxZ) -> buffer[1] = VoxelShapes.combineAndSimplify( buffer[1], VoxelShapes.cuboid( minX, minZ, 1 - maxY, maxX, maxZ, 1 - minY), BooleanBiFunction.OR));
break;
case Z:
buffer[0].forEachBox((minX, minY, minZ, maxX, maxY, maxZ) -> buffer[1] = VoxelShapes.combineAndSimplify( buffer[1], VoxelShapes.cuboid( minY, 1 - maxX, minZ, maxY, 1 - minX, maxZ), BooleanBiFunction.OR));
break;
}
return buffer[1];
AtomicReference<VoxelShape> new_shape = new AtomicReference<>(empty());
shape.forEachBox((minX, minY, minZ, maxX, maxY, maxZ) ->
new_shape.getAndUpdate(s ->
combineAndSimplify(
s,
switch (axis) {
case Y -> cuboid(minZ, minY, 1 - maxX, maxZ, maxY, 1 - minX);
case X -> cuboid(minX, minZ, 1 - maxY, maxX, maxZ, 1 - minY);
case Z -> cuboid(minY, 1 - maxX, minZ, maxY, 1 - minX, maxZ);
},
BooleanBiFunction.OR
)
)
);
return new_shape.get();
}
public static VoxelShape mirror(VoxelShape shape, Direction.Axis axis) {
VoxelShape[] buffer = new VoxelShape[]{ shape, VoxelShapes.empty() };
switch (axis) {
case Y:
buffer[0].forEachBox((minX, minY, minZ, maxX, maxY, maxZ) -> buffer[1] = VoxelShapes.combineAndSimplify( buffer[1], VoxelShapes.cuboid( minX, 1 - maxY, minZ, maxX, 1 - minY, maxZ), BooleanBiFunction.OR));
break;
case X:
buffer[0].forEachBox((minX, minY, minZ, maxX, maxY, maxZ) -> buffer[1] = VoxelShapes.combineAndSimplify( buffer[1], VoxelShapes.cuboid( 1 - maxX, minY, minZ, 1 - minX, maxY, maxZ), BooleanBiFunction.OR));
break;
case Z:
buffer[0].forEachBox((minX, minY, minZ, maxX, maxY, maxZ) -> buffer[1] = VoxelShapes.combineAndSimplify( buffer[1], VoxelShapes.cuboid( minX, minY, 1 - maxZ, maxX, maxY, 1 - minZ), BooleanBiFunction.OR));
break;
}
return buffer[1];
AtomicReference<VoxelShape> new_shape = new AtomicReference<>(empty());
shape.forEachBox((minX, minY, minZ, maxX, maxY, maxZ) ->
new_shape.getAndUpdate(s ->
combineAndSimplify(
s,
switch (axis) {
case Y -> cuboid(minX, 1 - maxY, minZ, maxX, 1 - minY, maxZ);
case X -> cuboid(1 - maxX, minY, minZ, 1 - minX, maxY, maxZ);
case Z -> cuboid(minX, minY, 1 - maxZ, maxX, maxY, 1 - minZ);
},
BooleanBiFunction.OR
)
)
);
return new_shape.get();
}
public static VoxelShape offset(VoxelShape shape, Direction.Axis axis, float offset) {
VoxelShape[] buffer = new VoxelShape[]{ shape, VoxelShapes.empty() };
switch (axis) {
case Y:
buffer[0].forEachBox((minX, minY, minZ, maxX, maxY, maxZ) -> buffer[1] = VoxelShapes.combineAndSimplify( buffer[1], VoxelShapes.cuboid( minX, offset + minY, minZ, maxX, offset + maxY, maxZ), BooleanBiFunction.OR));
break;
case X:
buffer[0].forEachBox((minX, minY, minZ, maxX, maxY, maxZ) -> buffer[1] = VoxelShapes.combineAndSimplify( buffer[1], VoxelShapes.cuboid( offset + minX, minY, minZ, offset + maxX, maxY, maxZ), BooleanBiFunction.OR));
break;
case Z:
buffer[0].forEachBox((minX, minY, minZ, maxX, maxY, maxZ) -> buffer[1] = VoxelShapes.combineAndSimplify( buffer[1], VoxelShapes.cuboid( minX, minY, offset + minZ, maxX, maxY, offset + maxZ), BooleanBiFunction.OR));
break;
AtomicReference<VoxelShape> new_shape = new AtomicReference<>(empty());
shape.forEachBox((minX, minY, minZ, maxX, maxY, maxZ) ->
new_shape.getAndUpdate(s ->
combineAndSimplify(
s,
switch (axis) {
case Y -> cuboid(minX, offset + minY, minZ, maxX, offset + maxY, maxZ);
case X -> cuboid(offset + minX, minY, minZ, offset + maxX, maxY, maxZ);
case Z -> cuboid(minX, minY, offset + minZ, maxX, maxY, offset + maxZ);
},
BooleanBiFunction.OR
)
)
);
return new_shape.get();
}
public static class VoxelListBuilder {
private final List<VoxelShape> voxels;
/**
* A simple class that helps to get cleaner shape list generation (Hopefully)
* @param base_shape - the shape to start with
* @param size - the amount of shapes expected
*/
private VoxelListBuilder(VoxelShape base_shape, int size) {
voxels = new ArrayList<>(size);
voxels.add(base_shape);
}
public static VoxelListBuilder create(VoxelShape base_shape, int size) {
return new VoxelListBuilder(base_shape, size);
}
/**
* Add shape by applying modification to previous shape
* @param modifications - the modifications to apply @See {@link VoxelHelper} methods
* @return this instance
*/
@SafeVarargs
public final VoxelListBuilder add(Function<VoxelShape, VoxelShape>... modifications) {
return add(voxels.size() - 1, modifications);
}
/**
* Add shape by applying modifications to given shape
* @param ref - the index of the reference shape
* @param modifications - the modifications to apply @See {@link VoxelHelper} methods
* @return this instance
*/
@SafeVarargs
public final VoxelListBuilder add(int ref, Function<VoxelShape, VoxelShape>... modifications) {
return add(voxels.get(ref), modifications);
}
/**
* Add shape by applying modifications to given shape
* @param ref - the shape you want to add and apply modifications if present
* @param modifications - the modifications to apply @See {@link VoxelHelper} methods
* @return this instance
*/
@SafeVarargs
public final VoxelListBuilder add(VoxelShape ref, Function<VoxelShape, VoxelShape>... modifications) {
for(Function<VoxelShape, VoxelShape> modif: modifications) {
ref = modif.apply(ref);
}
voxels.add(ref);
return this;
}
/**
* @return the final array of voxel shapes
*/
public VoxelShape[] build() {
return voxels.toArray(new VoxelShape[0]);
}
/**
* build a new set of voxels based on th negation of the references and a full cube
* @param ref_voxels - the reference to subtract from the wanted shape
* @return the array of complementary voxels
*/
public static VoxelShape[] buildFrom(VoxelShape[] ref_voxels) {
return buildFrom(VoxelShapes.fullCube(), ref_voxels);
}
/**
* build a new set of voxels based on th negation of the references and a wanted_shape
* @param ref_voxels - the reference to subtract from the wanted shape
* @return the array of complementary voxels
*/
public static VoxelShape[] buildFrom(VoxelShape wanted_shape, VoxelShape[] ref_voxels) {
VoxelShape[] shapes = new VoxelShape[ref_voxels.length];
for (int i = 0; i < shapes.length; i++) {
shapes[i] = VoxelShapes.combineAndSimplify(wanted_shape, ref_voxels[i], BooleanBiFunction.ONLY_FIRST);
}
return shapes;
}
return buffer[1];
}
}

View File

@@ -0,0 +1,391 @@
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;
import net.minecraft.block.Blocks;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.*;
import net.minecraft.sound.SoundCategory;
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.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<CullElement, Integer[]> 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());
Pair<Direction, Direction> sides = getHitSides(pos, side);
return Corner.getByDirections(side, sides.getLeft(), sides.getRight());
}
private static Pair<Direction, Direction> getHitSides(Vec3d pos, Direction side) {
Iterator<Direction.Axis> axes = Stream.of(Direction.Axis.values())
.filter(axis -> !axis.equals(side.getAxis())).iterator();
return new Pair<>(getHitDirection(axes.next(), pos), getHitDirection(axes.next(), pos));
}
public static Edge getPlacementEdge(ItemPlacementContext ctx) {
Direction side = ctx.getSide().getOpposite();
Vec3d pos = getHitPos(ctx.getHitPos(), ctx.getBlockPos());
Direction.Axis axis = getHitAxis(pos, side);
Direction part_direction = getHitDirection(axis, pos);
return Edge.getByDirections(side, part_direction);
}
public static Direction.Axis getHitAxis(Vec3d pos, Direction side) {
Stream<Direction.Axis> axes = Stream.of(Direction.Axis.values()).filter(axis -> !axis.equals(side.getAxis()));
return axes.reduce((axis_1, axis_2) ->
Math.abs(axis_1.choose(pos.x, pos.y, pos.z)) > Math.abs(axis_2.choose(pos.x, pos.y, pos.z))
? axis_1
: axis_2
).orElse(null);
}
public static Direction getHitDirection(Direction.Axis axis, Vec3d pos) {
return Direction.from(
axis,
axis.choose(pos.x, pos.y, pos.z) > 0
? Direction.AxisDirection.POSITIVE
: Direction.AxisDirection.NEGATIVE
);
}
public static Vec3d getRelativePos(Vec3d pos, BlockPos block_pos) {
return new Vec3d(
pos.getX() - block_pos.getX(),
pos.getY() - block_pos.getY(),
pos.getZ() - block_pos.getZ()
);
}
public static Vec3d getHitPos(Vec3d pos, BlockPos block_pos) {
pos = getRelativePos(pos, block_pos);
return new Vec3d(
pos.getX() - .5d,
pos.getY() - .5d,
pos.getZ() - .5d
);
}
public static StairShape getStairsShape(Edge face, BlockView world, BlockPos pos) {
StairShape shape = STRAIGHT;
String sol = getNeighborPos(face, face.getFirstDirection(), true, face.getSecondDirection(), world, pos);
switch (sol) {
case "right": return INNER_RIGHT;
case "left": return INNER_LEFT;
}
sol = getNeighborPos(face, face.getSecondDirection(), true, face.getFirstDirection(), world, pos);
switch (sol) {
case "right": return INNER_RIGHT;
case "left": return INNER_LEFT;
}
sol = getNeighborPos(face, face.getFirstDirection(), false, face.getSecondDirection(), world, pos);
switch (sol) {
case "right" -> shape = FIRST_OUTER_RIGHT;
case "left" -> shape = FIRST_OUTER_LEFT;
}
sol = getNeighborPos(face, face.getSecondDirection(), false, face.getFirstDirection(), world, pos);
switch (sol) {
case "right" -> {
if (shape.equals(STRAIGHT)) shape = SECOND_OUTER_RIGHT;
else if (shape.equals(FIRST_OUTER_RIGHT)) shape = OUTER_RIGHT;
}
case "left" -> {
if (shape.equals(STRAIGHT)) shape = SECOND_OUTER_LEFT;
else if (shape.equals(FIRST_OUTER_LEFT)) shape = OUTER_LEFT;
}
}
return shape;
}
public static String getNeighborPos(Edge face, Direction direction, Boolean reverse, Direction reference, BlockView world, BlockPos pos) {
BlockState block_state = world.getBlockState(
pos.offset(reverse ? direction.getOpposite() : direction)
);
if (isStair(block_state) && block_state.get(EDGE).hasDirection(reference)) {
if (block_state.get(EDGE).hasDirection(face.getLeftDirection())) return "left";
else if (block_state.get(EDGE).hasDirection(face.getRightDirection())) return "right";
}
return "";
}
public static boolean isStair(BlockState state) {
return state.getBlock() instanceof ReFramedStairBlock
|| state.getBlock() instanceof ReFramedStairsCubeBlock;
}
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);
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));
BlockState placement_state = block.getPlacementState(ctx);
if(placement_state != null && Block.isShapeFullCube(placement_state.getCollisionShape(world, pos)) && !(block instanceof BlockEntityProvider)) {
List<BlockState> themes = block_entity.getThemes();
if(!world.isClient) block_entity.setTheme(placement_state, theme_index);
// check for default light emission
if (placement_state.getLuminance() > 0
&& themes.stream().noneMatch(theme -> theme.getLuminance() > 0)
&& !block_entity.emitsLight()
)
block_entity.toggleLight();
world.setBlockState(pos, state.with(LIGHT, block_entity.emitsLight()));
// check for default redstone emission
if (placement_state.getWeakRedstonePower(world, pos, Direction.NORTH) > 0
&& themes.stream().noneMatch(theme -> theme.getWeakRedstonePower(world, pos, Direction.NORTH) > 0)
&& !block_entity.emitsRedstone()
) block_entity.toggleRedstone();
if(!player.isCreative()) held.decrement(1);
world.playSound(player, pos, placement_state.getSoundGroup().getPlaceSound(), SoundCategory.BLOCKS, 1f, 1.1f);
return ActionResult.SUCCESS;
}
}
return ActionResult.PASS;
}
public static ActionResult useUpgrade(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand) {
if(!(world.getBlockEntity(pos) instanceof ReFramedEntity block_entity)) return ActionResult.PASS;
ItemStack held = player.getStackInHand(hand);
ReframedInteractible ext = state.getBlock() instanceof ReframedInteractible e ? e : ReframedInteractible.Default.INSTANCE;
// frame will emit light if applied with glowstone
if(state.contains(LIGHT) && held.getItem() == Items.GLOWSTONE_DUST) {
block_entity.toggleLight();
world.setBlockState(pos, state.with(LIGHT, block_entity.emitsLight()));
world.playSound(player, pos, SoundEvents.BLOCK_GLASS_HIT, SoundCategory.BLOCKS, 1f, 1f);
return ActionResult.SUCCESS;
}
// frame will emit redstone if applied with redstone torch can deactivate redstone block camo emission
if(held.getItem() == Items.REDSTONE_TORCH && ext.canAddRedstoneEmission(state, world, pos)) {
block_entity.toggleRedstone();
world.playSound(player, pos, SoundEvents.BLOCK_LEVER_CLICK, SoundCategory.BLOCKS, 1f, 1f);
return ActionResult.SUCCESS;
}
// Frame will lose its collision if applied with popped chorus fruit
if(held.getItem() == Items.POPPED_CHORUS_FRUIT && ext.canRemoveCollision(state, world, pos)) {
block_entity.toggleSolidity();
world.playSound(player, pos, SoundEvents.ITEM_CHORUS_FRUIT_TELEPORT, SoundCategory.BLOCKS, 1f, 1f);
return ActionResult.SUCCESS;
}
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<ForwardingBakedModel> 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<List<QuadPosBounds>> 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<QuadPosBounds> 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<Direction.Axis, Double> axes = Arrays.stream(Direction.Axis.values())
.collect(Collectors.toMap(
x -> x,
x -> x.choose(pos.getX(), pos.getY(), pos.getZ())
));
return shape.getBoundingBoxes().stream()
.anyMatch(box ->
axes.keySet().stream()
.map(x -> box.getMin(x) <= axes.get(x) && box.getMax(x) >= axes.get(x))
.reduce((prev, current) -> prev && current).orElse(false)
);
}
}

View File

@@ -0,0 +1,13 @@
package fr.adrien1106.reframed.util.blocks;
import net.minecraft.state.property.BooleanProperty;
import net.minecraft.state.property.EnumProperty;
import net.minecraft.state.property.IntProperty;
public class BlockProperties {
public static final BooleanProperty LIGHT = BooleanProperty.of("emits_light");
public static final EnumProperty<Edge> EDGE = EnumProperty.of("edge", Edge.class);
public static final EnumProperty<Corner> CORNER = EnumProperty.of("corner", Corner.class);
public static final IntProperty CORNER_FACE = IntProperty.of("face", 0, 2);
public static final EnumProperty<StairShape> STAIR_SHAPE = EnumProperty.of("shape", StairShape.class);
}

View File

@@ -0,0 +1,97 @@
package fr.adrien1106.reframed.util.blocks;
import net.minecraft.util.StringIdentifiable;
import net.minecraft.util.math.Direction;
import java.util.Arrays;
public enum Corner implements StringIdentifiable {
NORTH_EAST_DOWN("north_east_down", Direction.NORTH, Direction.EAST, Direction.DOWN, 0),
EAST_SOUTH_DOWN("east_south_down", Direction.EAST, Direction.SOUTH, Direction.DOWN, 1),
SOUTH_WEST_DOWN("south_west_down", Direction.SOUTH, Direction.WEST, Direction.DOWN, 2),
WEST_NORTH_DOWN("west_north_down", Direction.WEST, Direction.NORTH, Direction.DOWN, 3),
NORTH_EAST_UP("north_east_up", Direction.NORTH, Direction.EAST, Direction.UP, 4),
EAST_SOUTH_UP("east_south_up", Direction.EAST, Direction.SOUTH, Direction.UP, 5),
SOUTH_WEST_UP("south_west_up", Direction.SOUTH, Direction.WEST, Direction.UP, 6),
WEST_NORTH_UP("west_north_up", Direction.WEST, Direction.NORTH, Direction.UP, 7);
private final String name;
private final Direction first_direction;
private final Direction second_direction;
private final Direction third_direction;
private final int ID;
Corner(String name, Direction first_direction, Direction second_direction, Direction third_direction, int id) {
this.name = name;
this.first_direction = first_direction;
this.second_direction = second_direction;
this.third_direction = third_direction;
this.ID = id;
}
public String asString() {
return this.name;
}
public String toString() {
return asString();
}
public boolean hasDirection(Direction direction) {
return this.first_direction.equals(direction)
|| this.second_direction.equals(direction)
|| this.third_direction.equals(direction);
}
public int getID() {
return this.ID;
}
public static Corner getByDirections(Direction direction_1, Direction direction_2, Direction direction_3) {
return Arrays.stream(Corner.values())
.filter(value -> value.hasDirection(direction_1) && value.hasDirection(direction_2) && value.hasDirection(direction_3))
.findFirst().orElse(Corner.NORTH_EAST_DOWN);
}
public static Corner fromId(int id) {
return Arrays.stream(Corner.values())
.filter(value -> value.getID() == id)
.findFirst().orElse(Corner.NORTH_EAST_DOWN);
}
public static Corner fromName(String name) {
return Arrays.stream(Corner.values())
.filter(value -> value.name().equals(name))
.findFirst().orElse(Corner.NORTH_EAST_DOWN);
}
public int getDirectionIndex(Direction side) {
return side == second_direction ? 1 : side == third_direction ? 2 : 0;
}
public Direction getDirection(int index) {
return index == 1 ? second_direction : index == 2 ? third_direction : first_direction;
}
public Corner getOpposite() {
return getByDirections(first_direction.getOpposite(), second_direction.getOpposite(), third_direction.getOpposite());
}
/**
* @param index - an index of the direction to keep and use as reference
* @return the opposite corner on same direction plane
*/
public Corner getOpposite(int index) {
return getOpposite(getDirection(index));
}
/**
* @param direction - a direction to keep and use as reference
* @return the opposite corner on same direction plane
*/
public Corner getOpposite(Direction direction) {
Direction other_1 = first_direction == direction ? second_direction : first_direction;
Direction other_2 = second_direction == direction || first_direction == direction ? third_direction : second_direction;
return getByDirections(direction, other_1.getOpposite(), other_2.getOpposite());
}
}

View File

@@ -1,37 +1,35 @@
package fr.adrien1106.reframed.util.property;
package fr.adrien1106.reframed.util.blocks;
import net.minecraft.util.StringIdentifiable;
import net.minecraft.util.math.Direction;
import java.util.Arrays;
public enum StairDirection implements StringIdentifiable {
NORTH_DOWN("north_down", Direction.NORTH, Direction.DOWN, Direction.EAST, 0),
DOWN_SOUTH("down_south", Direction.DOWN, Direction.SOUTH, Direction.EAST, 1),
SOUTH_UP("south_up", Direction.SOUTH, Direction.UP, Direction.EAST, 2),
UP_NORTH("up_north", Direction.UP, Direction.NORTH, Direction.EAST, 3),
WEST_DOWN("west_down", Direction.WEST, Direction.DOWN, Direction.SOUTH, 4),
DOWN_EAST("down_east", Direction.DOWN, Direction.EAST, Direction.SOUTH, 5),
EAST_UP("east_up", Direction.EAST, Direction.UP, Direction.SOUTH, 6),
UP_WEST("up_west", Direction.UP, Direction.WEST, Direction.SOUTH, 7),
WEST_NORTH("west_north", Direction.WEST, Direction.NORTH, Direction.DOWN, 8),
NORTH_EAST("north_east", Direction.NORTH, Direction.EAST, Direction.DOWN, 9),
EAST_SOUTH("east_south", Direction.EAST, Direction.SOUTH, Direction.DOWN, 10),
SOUTH_WEST("south_west", Direction.SOUTH, Direction.WEST, Direction.DOWN, 11);
public enum Edge implements StringIdentifiable {
NORTH_DOWN("north_down", Direction.NORTH, Direction.DOWN, Direction.Axis.X, 0),
DOWN_SOUTH("down_south", Direction.DOWN, Direction.SOUTH, Direction.Axis.X, 1),
SOUTH_UP("south_up", Direction.SOUTH, Direction.UP, Direction.Axis.X, 2),
UP_NORTH("up_north", Direction.UP, Direction.NORTH, Direction.Axis.X, 3),
WEST_DOWN("west_down", Direction.WEST, Direction.DOWN, Direction.Axis.Z, 4),
DOWN_EAST("down_east", Direction.DOWN, Direction.EAST, Direction.Axis.Z, 5),
EAST_UP("east_up", Direction.EAST, Direction.UP, Direction.Axis.Z, 6),
UP_WEST("up_west", Direction.UP, Direction.WEST, Direction.Axis.Z, 7),
WEST_NORTH("west_north", Direction.WEST, Direction.NORTH, Direction.Axis.Y, 8),
NORTH_EAST("north_east", Direction.NORTH, Direction.EAST, Direction.Axis.Y, 9),
EAST_SOUTH("east_south", Direction.EAST, Direction.SOUTH, Direction.Axis.Y, 10),
SOUTH_WEST("south_west", Direction.SOUTH, Direction.WEST, Direction.Axis.Y, 11);
private final String name;
private final Direction first_direction;
private final Direction second_direction;
private final Direction right_direction;
private final Direction left_direction;
private final Direction.Axis axis;
private final int ID;
StairDirection(String name, Direction first_direction, Direction second_direction, Direction right_direction, int id) {
Edge(String name, Direction first_direction, Direction second_direction, Direction.Axis axis, int id) {
this.name = name;
this.first_direction = first_direction;
this.second_direction = second_direction;
this.right_direction = right_direction;
this.left_direction = right_direction.getOpposite();
this.axis = axis;
this.ID = id;
}
@@ -51,10 +49,18 @@ public enum StairDirection implements StringIdentifiable {
return second_direction;
}
public Direction getRightDirection() {
return right_direction;
return switch (axis) {
case X -> Direction.WEST;
case Y -> Direction.DOWN;
case Z -> Direction.SOUTH;
};
}
public Direction getLeftDirection() {
return left_direction;
return switch (axis) {
case X -> Direction.EAST;
case Y -> Direction.UP;
case Z -> Direction.NORTH;
};
}
public boolean hasDirection(Direction direction) {
@@ -66,21 +72,21 @@ public enum StairDirection implements StringIdentifiable {
return this.ID;
}
public static StairDirection getByDirections(Direction direction_1, Direction direction_2) {
return Arrays.stream(StairDirection.values())
public static Edge getByDirections(Direction direction_1, Direction direction_2) {
return Arrays.stream(Edge.values())
.filter(value -> value.hasDirection(direction_1) && value.hasDirection(direction_2))
.findFirst().orElse(StairDirection.NORTH_DOWN);
.findFirst().orElse(Edge.NORTH_DOWN);
}
public static StairDirection fromId(int id) {
return Arrays.stream(StairDirection.values())
public static Edge fromId(int id) {
return Arrays.stream(Edge.values())
.filter(value -> value.getID() == id)
.findFirst().orElse(StairDirection.NORTH_DOWN);
.findFirst().orElse(Edge.NORTH_DOWN);
}
public static StairDirection fromName(String name) {
return Arrays.stream(StairDirection.values())
public static Edge fromName(String name) {
return Arrays.stream(Edge.values())
.filter(value -> value.name().equals(name))
.findFirst().orElse(StairDirection.NORTH_DOWN);
.findFirst().orElse(Edge.NORTH_DOWN);
}
}

View File

@@ -1,4 +1,4 @@
package fr.adrien1106.reframed.util;
package fr.adrien1106.reframed.util.blocks;
import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos;

Some files were not shown because too many files have changed in this diff Show More