Double frames #6

Merged
Adrien1106 merged 9 commits from dev into master 2024-03-02 02:02:47 +01:00
35 changed files with 846 additions and 365 deletions
Showing only changes of commit ce216d1f9f - Show all commits

View File

@ -24,16 +24,14 @@ import java.util.ArrayList;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/**
* TODO multiple camos
*/
public class ReFramed implements ModInitializer { public class ReFramed implements ModInitializer {
public static final String MODID = "reframed"; public static final String MODID = "reframed";
public static final ArrayList<Block> BLOCKS = new ArrayList<>(); 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, STAIRS, SLAB, DOUBLE_SLAB, POST, FENCE, FENCE_GATE, DOOR, TRAPDOOR, IRON_DOOR, IRON_TRAPDOOR, PRESSURE_PLATE, BUTTON, LEVER, WALL, CARPET, PANE, CANDLE;
public static BlockEntityType<ReFramedEntity> REFRAMED_BLOCK_ENTITY; public static BlockEntityType<ReFramedEntity> REFRAMED_BLOCK_ENTITY;
public static BlockEntityType<ReFramedDoubleEntity> REFRAMED_DOUBLE_BLOCK_ENTITY;
public static BiConsumer<World, BlockPos> chunkRerenderProxy = (world, pos) -> {}; public static BiConsumer<World, BlockPos> chunkRerenderProxy = (world, pos) -> {};
@ -47,6 +45,7 @@ public class ReFramed implements ModInitializer {
CUBE = registerReFramed("cube" , new ReFramedBlock(ReFramedInteractionUtil.makeSettings())); CUBE = registerReFramed("cube" , new ReFramedBlock(ReFramedInteractionUtil.makeSettings()));
STAIRS = registerReFramed("stairs" , new ReFramedStairsBlock(cp(Blocks.OAK_STAIRS))); STAIRS = registerReFramed("stairs" , new ReFramedStairsBlock(cp(Blocks.OAK_STAIRS)));
SLAB = registerReFramed("slab" , new ReFramedSlabBlock(cp(Blocks.OAK_SLAB))); SLAB = registerReFramed("slab" , new ReFramedSlabBlock(cp(Blocks.OAK_SLAB)));
DOUBLE_SLAB = registerReFramed("double_slab" , new ReFramedDoubleSlabBlock(cp(Blocks.OAK_SLAB)));
POST = registerReFramed("post" , new ReFramedPostBlock(cp(Blocks.OAK_FENCE))); POST = registerReFramed("post" , new ReFramedPostBlock(cp(Blocks.OAK_FENCE)));
FENCE = registerReFramed("fence" , new ReFramedFenceBlock(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))); FENCE_GATE = registerReFramed("fence_gate" , new ReFramedFenceGateBlock(cp(Blocks.OAK_FENCE_GATE)));
@ -63,7 +62,19 @@ public class ReFramed implements ModInitializer {
CANDLE = registerReFramed("candle" , new ReFramedCandleBlock(ReFramedCandleBlock.configureSettings(cp(Blocks.CANDLE)))); CANDLE = registerReFramed("candle" , new ReFramedCandleBlock(ReFramedCandleBlock.configureSettings(cp(Blocks.CANDLE))));
REFRAMED_BLOCK_ENTITY = Registry.register(Registries.BLOCK_ENTITY_TYPE, id("camo"), 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)
);
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)
); );
Registry.register(Registries.ITEM_GROUP, id("tab"), FabricItemGroup.builder() Registry.register(Registries.ITEM_GROUP, id("tab"), FabricItemGroup.builder()

View File

@ -1,29 +1,44 @@
package fr.adrien1106.reframed.block; package fr.adrien1106.reframed.block;
import com.google.common.base.MoreObjects;
import fr.adrien1106.reframed.ReFramed; import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.util.ReFramedInteractionUtil; import fr.adrien1106.reframed.util.ReframedInteractible;
import fr.adrien1106.reframed.util.ThemeableBlockEntity;
import net.minecraft.block.*; import net.minecraft.block.*;
import net.minecraft.block.entity.BlockEntity; import net.minecraft.block.entity.BlockEntity;
import net.minecraft.entity.LivingEntity; import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemPlacementContext; import net.minecraft.item.*;
import net.minecraft.item.ItemStack; import net.minecraft.nbt.NbtCompound;
import net.minecraft.sound.SoundCategory;
import net.minecraft.sound.SoundEvents;
import net.minecraft.state.StateManager; import net.minecraft.state.StateManager;
import net.minecraft.state.property.BooleanProperty;
import net.minecraft.util.ActionResult; import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand; import net.minecraft.util.Hand;
import net.minecraft.util.ItemScatterer;
import net.minecraft.util.collection.DefaultedList;
import net.minecraft.util.function.BooleanBiFunction;
import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction; import net.minecraft.util.math.Direction;
import net.minecraft.util.shape.VoxelShape; import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes;
import net.minecraft.world.BlockView; import net.minecraft.world.BlockView;
import net.minecraft.world.GameRules;
import net.minecraft.world.World; import net.minecraft.world.World;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.List;
import static net.minecraft.util.shape.VoxelShapes.*;
public class ReFramedBlock extends Block implements BlockEntityProvider { public class ReFramedBlock extends Block implements BlockEntityProvider {
public static final BooleanProperty LIGHT = BooleanProperty.of("frame_light");
public ReFramedBlock(Settings settings) { public ReFramedBlock(Settings settings) {
super(settings); super(settings);
setDefaultState(ReFramedInteractionUtil.setDefaultStates(getDefaultState())); setDefaultState(getDefaultState().with(LIGHT, false));
} }
//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 //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 +50,262 @@ public class ReFramedBlock extends Block implements BlockEntityProvider {
@Override @Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) { protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(ReFramedInteractionUtil.appendProperties(builder)); super.appendProperties(builder.add(LIGHT));
} }
@Nullable @Nullable
@Override @Override
public BlockState getPlacementState(ItemPlacementContext ctx) { public BlockState getPlacementState(ItemPlacementContext ctx) {
return ReFramedInteractionUtil.modifyPlacementState(super.getPlacementState(ctx), ctx); return ReFramedEntity.getNbtLightLevel(super.getPlacementState(ctx), ctx.getStack());
} }
@Override @Override
public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) { 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 (!canUse(world, pos, player)) return superUse(state, world, pos, player, hand, hit);
if(!r.isAccepted()) r = super.onUse(state, world, pos, player, hand, hit); ActionResult result = useUpgrade(state, world, pos, player, hand);
return r; if (result.isAccepted()) return result;
return 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);
}
protected 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()));
if(!player.isCreative())
if (block_entity.emitsLight()) held.decrement(1);
else held.increment(1);
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();
if(!player.isCreative())
if (block_entity.emitsRedstone()) held.decrement(1);
else held.increment(1);
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();
if(!player.isCreative())
if (!block_entity.isSolid()) held.decrement(1);
else held.increment(1);
world.playSound(player, pos, SoundEvents.ITEM_CHORUS_FRUIT_TELEPORT, SoundCategory.BLOCKS, 1f, 1f);
return ActionResult.SUCCESS;
}
return ActionResult.PASS;
}
protected 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 && 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))
if (block_entity.emitsLight()) Block.dropStack(world, pos, new ItemStack(Items.GLOWSTONE_DUST));
else 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))
if (block_entity.emitsRedstone()) Block.dropStack(world, pos, new ItemStack(Items.GLOWSTONE_DUST));
else 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;
} }
@Override @Override
public void onStateReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) { 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(theme -> theme.isSolid()))
drops.add(new ItemStack(Items.POPPED_CHORUS_FRUIT));
ItemScatterer.spawn(world, pos, drops);
}
super.onStateReplaced(state, world, pos, newState, moved); super.onStateReplaced(state, world, pos, newState, moved);
} }
@Override @Override
public void onPlaced(World world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { 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); super.onPlaced(world, pos, state, placer, stack);
} }
@Override @Override
public VoxelShape getCollisionShape(BlockState state, BlockView view, BlockPos pos, ShapeContext ctx) { 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 @Override
public boolean emitsRedstonePower(BlockState state) { public VoxelShape getCullingShape(BlockState state, BlockView view, BlockPos pos) {
return ReFramedInteractionUtil.emitsRedstonePower(state); 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 @Override
public int getWeakRedstonePower(BlockState state, BlockView view, BlockPos pos, Direction dir) { 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 @Override
public int getStrongRedstonePower(BlockState state, BlockView view, BlockPos pos, Direction dir) { 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;
}
// Doing this method from scratch as it is simpler to do than injecting everywhere
public static boolean shouldDrawSide(BlockState self_state, BlockView world, BlockPos pos, Direction side, BlockPos other_pos, int theme_index) {
ThemeableBlockEntity self = world.getBlockEntity(pos) instanceof ThemeableBlockEntity e ? e : null;
ThemeableBlockEntity other = world.getBlockEntity(other_pos) instanceof ThemeableBlockEntity e ? e : null;
BlockState other_state = world.getBlockState(other_pos);
// normal behaviour
if (self == null && other == null) return 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 (self_theme.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;
} }
} }

View File

@ -0,0 +1,96 @@
package fr.adrien1106.reframed.block;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.util.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 java.util.Arrays;
import java.util.Map;
import java.util.stream.Collectors;
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 getHitPart(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 = hit.getPos();
BlockPos origin = hit.getBlockPos();
Map<Direction.Axis, Double> axes = Arrays.stream(Direction.Axis.values())
.filter(axis -> axis != side.getAxis())
.collect(Collectors.toMap(
axis -> axis,
axis -> axis.choose(pos.getX() - origin.getX(), pos.getY() - origin.getY(), pos.getZ() - origin.getZ()))
);
if (matchesFace(first_shape.getFace(side), axes)) return 1;
if (matchesFace(second_shape.getFace(side), axes)) return 2;
return 0;
}
private static boolean matchesFace(VoxelShape shape, Map<Direction.Axis, Double> axes) {
return shape.getBoundingBoxes().stream()
.anyMatch(box ->
axes.keySet().stream()
.map(axis -> box.getMin(axis) <= axes.get(axis) && box.getMax(axis) >= axes.get(axis))
.reduce((prev, current) -> prev && current).get()
);
}
@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, getHitPart(state, hit));
}
@Override
public VoxelShape getCollisionShape(BlockState state, BlockView view, BlockPos pos, ShapeContext ctx) {
return isGhost(view, pos) ? empty() : fullCube();
}
@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 = useUpgrade(state, world, pos, player, hand);
if (result.isAccepted()) return result;
return useCamo(state, world, pos, player, hand, hit, getHitPart(state, hit));
}
}

View File

@ -9,6 +9,7 @@ import net.minecraft.nbt.NbtHelper;
import net.minecraft.registry.Registries; import net.minecraft.registry.Registries;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import java.util.List;
import java.util.Objects; import java.util.Objects;
public class ReFramedDoubleEntity extends ReFramedEntity { public class ReFramedDoubleEntity extends ReFramedEntity {
@ -20,15 +21,23 @@ public class ReFramedDoubleEntity extends ReFramedEntity {
} }
@Override @Override
public BlockState getSecondTheme() { public BlockState getTheme(int i) {
return second_state; return i == 2 ? second_state : super.getTheme(i);
} }
public void setSecondTheme(BlockState newState) { @Override
if(!Objects.equals(second_state, newState)) { public List<BlockState> getThemes() {
second_state = newState; 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(); markDirtyAndDispatch();
} } else super.setTheme(new_state, i);
} }
@Override @Override
@ -36,7 +45,7 @@ public class ReFramedDoubleEntity extends ReFramedEntity {
super.readNbt(nbt); super.readNbt(nbt);
BlockState rendered_state = second_state;// keep previous state to check if rerender is needed BlockState rendered_state = second_state;// keep previous state to check if rerender is needed
first_state = NbtHelper.toBlockState(Registries.BLOCK.getReadOnlyWrapper(), nbt.getCompound(BLOCKSTATE_KEY + 2)); 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 // Force a chunk remesh on the client if the displayed blockstate has changed
if(world != null && world.isClient && !Objects.equals(rendered_state, second_state)) { if(world != null && world.isClient && !Objects.equals(rendered_state, second_state)) {

View File

@ -0,0 +1,64 @@
package fr.adrien1106.reframed.block;
import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.generator.GBlockstate;
import fr.adrien1106.reframed.generator.MultipartBlockStateProvider;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.data.client.MultipartBlockStateSupplier;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.state.StateManager;
import net.minecraft.state.property.Properties;
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.*;
import static net.minecraft.state.property.Properties.AXIS;
public class ReFramedDoubleSlabBlock extends ReFramedDoubleBlock implements MultipartBlockStateProvider {
public ReFramedDoubleSlabBlock(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));
}
@Nullable
@Override
public BlockState getPlacementState(ItemPlacementContext ctx) {
return super.getPlacementState(ctx).with(Properties.AXIS, ctx.getSide().getAxis());
}
@Override
public VoxelShape getShape(BlockState state, int i) {
return switch (state.get(Properties.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));
}
}

View File

@ -1,7 +1,6 @@
package fr.adrien1106.reframed.block; package fr.adrien1106.reframed.block;
import fr.adrien1106.reframed.ReFramed; import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.util.ReFramedInteractionUtil;
import fr.adrien1106.reframed.util.ThemeableBlockEntity; import fr.adrien1106.reframed.util.ThemeableBlockEntity;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks; import net.minecraft.block.Blocks;
@ -21,6 +20,8 @@ import net.minecraft.util.math.BlockPos;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects; import java.util.Objects;
//Keeping the weight of this block entity down, both in terms of memory consumption and NBT sync traffic, //Keeping the weight of this block entity down, both in terms of memory consumption and NBT sync traffic,
@ -28,7 +29,7 @@ import java.util.Objects;
//To that end, most of the state has been crammed into a bitfield. //To that end, most of the state has been crammed into a bitfield.
public class ReFramedEntity extends BlockEntity implements ThemeableBlockEntity { public class ReFramedEntity extends BlockEntity implements ThemeableBlockEntity {
protected BlockState first_state = Blocks.AIR.getDefaultState(); protected BlockState first_state = Blocks.AIR.getDefaultState();
protected byte bitfield = SOLIDITY_MASK; protected byte bit_field = SOLIDITY_MASK;
protected static final byte LIGHT_MASK = 0b001; protected static final byte LIGHT_MASK = 0b001;
protected static final byte REDSTONE_MASK = 0b010; protected static final byte REDSTONE_MASK = 0b010;
@ -47,7 +48,7 @@ public class ReFramedEntity extends BlockEntity implements ThemeableBlockEntity
BlockState rendered_state = first_state; // keep previous state to check if rerender is needed BlockState rendered_state = first_state; // keep previous state to check if rerender is needed
first_state = NbtHelper.toBlockState(Registries.BLOCK.getReadOnlyWrapper(), nbt.getCompound(BLOCKSTATE_KEY + 1)); first_state = NbtHelper.toBlockState(Registries.BLOCK.getReadOnlyWrapper(), nbt.getCompound(BLOCKSTATE_KEY + 1));
if (nbt.contains(BITFIELD_KEY)) bitfield = nbt.getByte(BITFIELD_KEY); if (nbt.contains(BITFIELD_KEY)) bit_field = nbt.getByte(BITFIELD_KEY);
// Force a chunk remesh on the client if the displayed blockstate has changed // Force a chunk remesh on the client if the displayed blockstate has changed
if(world != null && world.isClient && !Objects.equals(rendered_state, first_state)) { if(world != null && world.isClient && !Objects.equals(rendered_state, first_state)) {
@ -60,35 +61,34 @@ public class ReFramedEntity extends BlockEntity implements ThemeableBlockEntity
super.writeNbt(nbt); super.writeNbt(nbt);
if(first_state != Blocks.AIR.getDefaultState()) nbt.put(BLOCKSTATE_KEY + 1, NbtHelper.fromBlockState(first_state)); if(first_state != Blocks.AIR.getDefaultState()) nbt.put(BLOCKSTATE_KEY + 1, NbtHelper.fromBlockState(first_state));
if(bitfield != SOLIDITY_MASK) nbt.putByte(BITFIELD_KEY, bitfield); if(bit_field != SOLIDITY_MASK) nbt.putByte(BITFIELD_KEY, bit_field);
} }
// TODO revisit public static @NotNull BlockState readStateFromItem(ItemStack stack, int state) {
public static @NotNull BlockState readStateFromItem(ItemStack stack) { NbtCompound nbt = BlockItem.getBlockEntityNbt(stack);
NbtCompound blockEntityTag = BlockItem.getBlockEntityNbt(stack); if(nbt == null) return Blocks.AIR.getDefaultState();
if(blockEntityTag == null) return Blocks.AIR.getDefaultState();
//slightly paranoid NBT handling cause you never know what mysteries are afoot with items //slightly paranoid NBT handling cause you never know what mysteries are afoot with items
NbtElement subElement; NbtElement element;
if(blockEntityTag.contains(BLOCKSTATE_KEY)) subElement = blockEntityTag.get(BLOCKSTATE_KEY + 1); if(nbt.contains(BLOCKSTATE_KEY + state)) element = nbt.get(BLOCKSTATE_KEY + state);
else return Blocks.AIR.getDefaultState(); else return Blocks.AIR.getDefaultState();
if(!(subElement instanceof NbtCompound subCompound)) return Blocks.AIR.getDefaultState(); if(!(element instanceof NbtCompound compound)) return Blocks.AIR.getDefaultState();
else return NbtHelper.toBlockState(Registries.BLOCK.getReadOnlyWrapper(), subCompound); 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 //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 //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 //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" //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; if(state == null || stack == null) return state;
NbtCompound nbt = BlockItem.getBlockEntityNbt(stack); NbtCompound nbt = BlockItem.getBlockEntityNbt(stack);
if(nbt == null) return state; if(nbt == null) return state;
if(state.contains(ReFramedInteractionUtil.LIGHT)) { if(state.contains(ReFramedBlock.LIGHT)) {
state = state.with(ReFramedInteractionUtil.LIGHT, state = state.with(ReFramedBlock.LIGHT,
((nbt.contains(BITFIELD_KEY) ((nbt.contains(BITFIELD_KEY)
? nbt.getByte(BITFIELD_KEY) ? nbt.getByte(BITFIELD_KEY)
: SOLIDITY_MASK) : SOLIDITY_MASK)
@ -100,47 +100,57 @@ public class ReFramedEntity extends BlockEntity implements ThemeableBlockEntity
} }
@Override @Override
public BlockState getFirstTheme() { public BlockState getTheme(int i) {
return first_state; return first_state;
} }
public void setFirstTheme(BlockState newState) { @Override
if(!Objects.equals(first_state, newState)) { public List<BlockState> getThemes() {
first_state = newState; 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(); markDirtyAndDispatch();
} }
} }
/* --------------------------------------------------- ADDONS --------------------------------------------------- */ /* --------------------------------------------------- ADDONS --------------------------------------------------- */
public boolean emitsLight() { public boolean emitsLight() {
return (bitfield & LIGHT_MASK) != 0; return (bit_field & LIGHT_MASK) != 0;
} }
public void toggleLight() { public void toggleLight() {
bitfield |= emitsLight() ? ~LIGHT_MASK: LIGHT_MASK; if (emitsLight()) bit_field &= ~LIGHT_MASK;
else bit_field |= LIGHT_MASK;
markDirtyAndDispatch(); markDirtyAndDispatch();
} }
public void toggleRedstone() { public void toggleRedstone() {
bitfield |= emitsRedstone() ? ~REDSTONE_MASK: REDSTONE_MASK; if (emitsRedstone()) bit_field &= ~REDSTONE_MASK;
else bit_field |= REDSTONE_MASK;
if(world != null) world.updateNeighbors(pos, getCachedState().getBlock()); if(world != null) world.updateNeighbors(pos, getCachedState().getBlock());
markDirtyAndDispatch(); markDirtyAndDispatch();
} }
public boolean emitsRedstone() { public boolean emitsRedstone() {
return (bitfield & REDSTONE_MASK) != 0; return (bit_field & REDSTONE_MASK) != 0;
} }
public void toggleSolidity() { public void toggleSolidity() {
bitfield |= isSolid() ? ~SOLIDITY_MASK: SOLIDITY_MASK; if (isSolid()) bit_field &= ~SOLIDITY_MASK;
else bit_field |= SOLIDITY_MASK;
if(world != null) world.setBlockState(pos, getCachedState()); if(world != null) world.setBlockState(pos, getCachedState());
markDirtyAndDispatch(); markDirtyAndDispatch();
} }
public boolean isSolid() { public boolean isSolid() {
return (bitfield & SOLIDITY_MASK) != 0; return (bit_field & SOLIDITY_MASK) != 0;
} }
@Nullable @Nullable

View File

@ -1,11 +1,15 @@
package fr.adrien1106.reframed.block; 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.generator.MultipartBlockStateProvider;
import net.minecraft.block.*; 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.MultipartBlockStateSupplier;
import net.minecraft.item.ItemPlacementContext; import net.minecraft.item.ItemPlacementContext;
import net.minecraft.state.StateManager; 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.BlockPos;
import net.minecraft.util.math.Direction; import net.minecraft.util.math.Direction;
import net.minecraft.util.shape.VoxelShape; import net.minecraft.util.shape.VoxelShape;
@ -13,34 +17,37 @@ import net.minecraft.util.shape.VoxelShapes;
import net.minecraft.world.BlockView; import net.minecraft.world.BlockView;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import static net.minecraft.data.client.VariantSettings.Rotation.*;
import static net.minecraft.state.property.Properties.FACING;
public class ReFramedSlabBlock extends WaterloggableReFramedBlock implements MultipartBlockStateProvider { public class ReFramedSlabBlock extends WaterloggableReFramedBlock implements MultipartBlockStateProvider {
private static final VoxelShape DOWN = VoxelShapes.cuboid(0f, 0f, 0f, 1f, 0.5f, 1f); protected 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); protected 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); protected 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); protected 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); protected 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); protected static final VoxelShape WEST = VoxelShapes.cuboid(0f, 0f, 0f, 0.5f, 1f, 1f);
public ReFramedSlabBlock(Settings settings) { public ReFramedSlabBlock(Settings settings) {
super(settings); super(settings);
setDefaultState(getDefaultState().with(Properties.FACING, Direction.DOWN)); setDefaultState(getDefaultState().with(FACING, Direction.DOWN));
} }
@Override @Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) { protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
super.appendProperties(builder.add(Properties.FACING)); super.appendProperties(builder.add(FACING));
} }
@Nullable @Nullable
@Override @Override
public BlockState getPlacementState(ItemPlacementContext ctx) { 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 @Override
public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) { public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return switch (state.get(Properties.FACING)) { return switch (state.get(FACING)) {
case DOWN -> DOWN; case DOWN -> DOWN;
case UP -> UP; case UP -> UP;
case NORTH -> NORTH; case NORTH -> NORTH;
@ -52,6 +59,19 @@ public class ReFramedSlabBlock extends WaterloggableReFramedBlock implements Mul
@Override @Override
public MultipartBlockStateSupplier getMultipart() { 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));
} }
} }

View File

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

View File

@ -53,6 +53,7 @@ public class ReFramedClient implements ClientModInitializer {
HELPER.addReFramedModel(ReFramed.id("pressure_plate_up_special") , HELPER.auto(new Identifier("block/pressure_plate_up"))); 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("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("slab_special") , HELPER.auto(new Identifier("block/slab")));
HELPER.addReFramedModel(ReFramed.id("double_slab_special") , HELPER.autoDouble(new Identifier("block/slab"), new Identifier("block/slab_top")));
HELPER.addReFramedModel(ReFramed.id("stairs_special") , HELPER.auto(ReFramed.id("block/stairs"))); HELPER.addReFramedModel(ReFramed.id("stairs_special") , HELPER.auto(ReFramed.id("block/stairs")));
HELPER.addReFramedModel(ReFramed.id("double_outer_stairs_special") , HELPER.auto(ReFramed.id("block/double_outer_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(ReFramed.id("block/inner_stairs"))); HELPER.addReFramedModel(ReFramed.id("inner_stairs_special") , HELPER.auto(ReFramed.id("block/inner_stairs")));
@ -89,6 +90,7 @@ public class ReFramedClient implements ClientModInitializer {
HELPER.assignItemModel(ReFramed.id("fence_post_inventory_special") , ReFramed.POST); 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("pressure_plate_up_special") , ReFramed.PRESSURE_PLATE);
HELPER.assignItemModel(ReFramed.id("slab_special") , ReFramed.SLAB); HELPER.assignItemModel(ReFramed.id("slab_special") , ReFramed.SLAB);
HELPER.assignItemModel(ReFramed.id("double_slab_special") , ReFramed.DOUBLE_SLAB);
HELPER.assignItemModel(ReFramed.id("stairs_special") , ReFramed.STAIRS); HELPER.assignItemModel(ReFramed.id("stairs_special") , ReFramed.STAIRS);
HELPER.assignItemModel(ReFramed.id("trapdoor_bottom_special") , ReFramed.TRAPDOOR); HELPER.assignItemModel(ReFramed.id("trapdoor_bottom_special") , ReFramed.TRAPDOOR);
HELPER.assignItemModel(ReFramed.id("wall_inventory_special") , ReFramed.WALL); HELPER.assignItemModel(ReFramed.id("wall_inventory_special") , ReFramed.WALL);

View File

@ -1,13 +1,13 @@
package fr.adrien1106.reframed.client; package fr.adrien1106.reframed.client;
import fr.adrien1106.reframed.client.model.apperance.CamoAppearanceManager;
import fr.adrien1106.reframed.client.model.UnbakedAutoRetexturedModel; 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.UnbakedJsonRetexturedModel;
import fr.adrien1106.reframed.util.ThemeableBlockEntity; 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.Renderer;
import net.fabricmc.fabric.api.renderer.v1.RendererAccess; import net.fabricmc.fabric.api.renderer.v1.RendererAccess;
import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.model.UnbakedModel; import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.client.texture.Sprite; import net.minecraft.client.texture.Sprite;
import net.minecraft.client.util.SpriteIdentifier; import net.minecraft.client.util.SpriteIdentifier;
@ -25,20 +25,17 @@ public class ReFramedClientHelper {
private final ReFramedModelProvider prov; private final ReFramedModelProvider prov;
public UnbakedModel auto(Identifier parent) {
return auto(parent, ThemeableBlockEntity::getFirstTheme); public UnbakedRetexturedModel auto(Identifier parent) {
return new UnbakedAutoRetexturedModel(parent);
} }
public UnbakedModel auto(Identifier parent, Function<ThemeableBlockEntity, BlockState> state_getter) { public UnbakedRetexturedModel json(Identifier parent) {
return new UnbakedAutoRetexturedModel(parent, state_getter); return new UnbakedJsonRetexturedModel(parent);
} }
public UnbakedModel json(Identifier parent) { public UnbakedModel autoDouble(Identifier first, Identifier second) {
return json(parent, ThemeableBlockEntity::getFirstTheme); return new UnbakedDoubleRetexturedModel(auto(first), auto(second));
}
public UnbakedModel json(Identifier parent, Function<ThemeableBlockEntity, BlockState> state_getter) {
return new UnbakedJsonRetexturedModel(parent, state_getter);
} }
public void addReFramedModel(Identifier id, UnbakedModel unbaked) { public void addReFramedModel(Identifier id, UnbakedModel unbaked) {

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. //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. //So, instead, we use a ModelVariantProvider to redirect attempts to load the item:id#inventory model.
@Override @Override
public @Nullable UnbakedModel loadModelVariant(ModelIdentifier modelId, ModelProviderContext context) { public @Nullable UnbakedModel loadModelVariant(ModelIdentifier model, ModelProviderContext context) {
Identifier customModelId = itemAssignments.get(modelId); Identifier custom_model = itemAssignments.get(model);
return customModelId == null ? null : loadModelResource(customModelId, context); return custom_model == null ? null : loadModelResource(custom_model, context);
} }
/// camo appearance manager cache /// camo appearance manager cache

View File

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

View File

@ -1,83 +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, RetexturingBakedModel.RetexturingTransformer transform) {
MeshBuilder builder = ReFramedClient.HELPER.getFabricRenderer().meshBuilder();
QuadEmitter emitter = builder.getEmitter();
mesh.forEach(quad -> {
int i = -1;
do {
emitter.copyFrom(quad);
i = transform.transform(emitter, i);
} while (i > 0);
});
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.minecraft.client.render.model.BakedModel;
import java.util.List;
public interface MultiRetexturableModel {
List<BakedModel> models();
}

View File

@ -1,6 +1,7 @@
package fr.adrien1106.reframed.client.model; package fr.adrien1106.reframed.client.model;
import fr.adrien1106.reframed.block.ReFramedEntity; import fr.adrien1106.reframed.block.ReFramedEntity;
import fr.adrien1106.reframed.client.ReFramedClient;
import fr.adrien1106.reframed.client.model.apperance.SpriteProperties; import fr.adrien1106.reframed.client.model.apperance.SpriteProperties;
import fr.adrien1106.reframed.mixin.MinecraftAccessor; import fr.adrien1106.reframed.mixin.MinecraftAccessor;
import fr.adrien1106.reframed.client.model.apperance.CamoAppearance; import fr.adrien1106.reframed.client.model.apperance.CamoAppearance;
@ -8,6 +9,7 @@ import fr.adrien1106.reframed.client.model.apperance.CamoAppearanceManager;
import fr.adrien1106.reframed.client.model.apperance.WeightedComputedAppearance; import fr.adrien1106.reframed.client.model.apperance.WeightedComputedAppearance;
import fr.adrien1106.reframed.util.ThemeableBlockEntity; import fr.adrien1106.reframed.util.ThemeableBlockEntity;
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh; 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.MutableQuadView; import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter; import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel; import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
@ -27,39 +29,41 @@ import net.minecraft.world.BlockRenderView;
import java.util.List; import java.util.List;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
public abstract class RetexturingBakedModel extends ForwardingBakedModel { public abstract class RetexturingBakedModel extends ForwardingBakedModel {
public RetexturingBakedModel(BakedModel baseModel, CamoAppearanceManager tam, Function<ThemeableBlockEntity, BlockState> state_getter, ModelBakeSettings settings, BlockState itemModelState, boolean ao) { public RetexturingBakedModel(BakedModel base_model, CamoAppearanceManager tam, int theme_index, ModelBakeSettings settings, BlockState item_state, boolean ao) {
this.wrapped = baseModel; //field from the superclass; vanilla getQuads etc. will delegate through to this this.wrapped = base_model; //field from the superclass; vanilla getQuads etc. will delegate through to this
this.tam = tam; this.tam = tam;
this.state_getter = state_getter; this.theme_index = theme_index;
this.uvlock = settings.isUvLocked(); this.uv_lock = settings.isUvLocked();
this.itemModelState = itemModelState; this.item_state = item_state;
this.ao = ao; this.ao = ao;
} }
protected final CamoAppearanceManager tam; protected final CamoAppearanceManager tam;
protected final Function<ThemeableBlockEntity, BlockState> state_getter; protected final int theme_index;
protected final boolean uvlock; protected final boolean uv_lock;
protected final BlockState itemModelState; protected final BlockState item_state;
protected final boolean ao; protected final boolean ao;
/* ----------------------------------------------- CACHE ELEMENT ------------------------------------------------ */
// TODO make static ? for connected textures ?
protected record MeshCacheKey(BlockState state, TransformCacheKey transform) {} 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 record TransformCacheKey(CamoAppearance appearance, int model_id) {}
protected final ConcurrentMap<TransformCacheKey, RetexturingTransformer> retextured_transforms = new ConcurrentHashMap<>(); protected final ConcurrentMap<TransformCacheKey, RetexturingTransformer> retextured_transforms = new ConcurrentHashMap<>();
protected final ConcurrentMap<MeshCacheKey, Mesh> retextured_meshes = new ConcurrentHashMap<>(); //mutable, append-only cache
protected static final Direction[] DIRECTIONS = Direction.values(); protected static final Direction[] DIRECTIONS_AND_NULL;
protected static final Direction[] DIRECTIONS_AND_NULL = new Direction[DIRECTIONS.length + 1]; static {
static { System.arraycopy(DIRECTIONS, 0, DIRECTIONS_AND_NULL, 0, DIRECTIONS.length); } Direction[] values = Direction.values();
DIRECTIONS_AND_NULL = new Direction[values.length + 1];
System.arraycopy(values, 0, DIRECTIONS_AND_NULL, 0, values.length);
}
protected final ConcurrentMap<BlockState, Mesh> jsonToMesh = new ConcurrentHashMap<>(); protected final ConcurrentMap<BlockState, Mesh> jsonToMesh = new ConcurrentHashMap<>();
protected Mesh getBaseMesh(BlockState state) { protected Mesh getBaseMesh(BlockState state) {
//Convert models to re-texturable Meshes lazily, the first time we encounter each blockstate //Convert models to re-texturable Meshes lazily, the first time we encounter each blockstate
return jsonToMesh.computeIfAbsent(state, this::convertModel); return jsonToMesh.computeIfAbsent(state, this::convertModel);
@ -74,15 +78,15 @@ public abstract class RetexturingBakedModel extends ForwardingBakedModel {
@Override @Override
public Sprite getParticleSprite() { public Sprite getParticleSprite() {
return tam.getDefaultAppearance().getSprites(Direction.UP, 0).get(0).sprite(); return tam.getDefaultAppearance(theme_index).getSprites(Direction.UP, 0).get(0).sprite();
} }
@Override @Override
public void emitBlockQuads(BlockRenderView world, BlockState state, BlockPos pos, Supplier<Random> randomSupplier, RenderContext context) { public void emitBlockQuads(BlockRenderView world, BlockState state, BlockPos pos, Supplier<Random> randomSupplier, RenderContext context) {
BlockState theme = (world.getBlockEntity(pos) instanceof ThemeableBlockEntity s) ? state_getter.apply(s) : null; BlockState theme = (world.getBlockEntity(pos) instanceof ThemeableBlockEntity s) ? s.getTheme(theme_index) : null;
QuadEmitter quad_emitter = context.getEmitter(); QuadEmitter quad_emitter = context.getEmitter();
if(theme == null || theme.isAir()) { if(theme == null || theme.isAir()) {
getUntintedRetexturedMesh(new MeshCacheKey(state, new TransformCacheKey(tam.getDefaultAppearance(), 0)), 0).outputTo(quad_emitter); getUntintedRetexturedMesh(new MeshCacheKey(state, new TransformCacheKey(tam.getDefaultAppearance(theme_index), 0)), 0).outputTo(quad_emitter);
return; return;
} }
if(theme.getBlock() == Blocks.BARRIER) return; if(theme.getBlock() == Blocks.BARRIER) return;
@ -118,16 +122,16 @@ public abstract class RetexturingBakedModel extends ForwardingBakedModel {
//none of this is accessible unless you're in creative mode doing ctrl-pick btw //none of this is accessible unless you're in creative mode doing ctrl-pick btw
CamoAppearance nbtAppearance; CamoAppearance nbtAppearance;
int tint; int tint;
BlockState theme = ReFramedEntity.readStateFromItem(stack); // TODO Different states for both models BlockState theme = ReFramedEntity.readStateFromItem(stack, theme_index);
if(!theme.isAir()) { if(!theme.isAir()) {
nbtAppearance = tam.getCamoAppearance(null, theme, null); nbtAppearance = tam.getCamoAppearance(null, theme, null);
tint = 0xFF000000 | ((MinecraftAccessor) MinecraftClient.getInstance()).getItemColors().getColor(new ItemStack(theme.getBlock()), 0); tint = 0xFF000000 | ((MinecraftAccessor) MinecraftClient.getInstance()).getItemColors().getColor(new ItemStack(theme.getBlock()), 0);
} else { } else {
nbtAppearance = tam.getDefaultAppearance(); nbtAppearance = tam.getDefaultAppearance(theme_index);
tint = 0xFFFFFFFF; tint = 0xFFFFFFFF;
} }
Mesh untintedMesh = getUntintedRetexturedMesh(new MeshCacheKey(itemModelState, new TransformCacheKey(nbtAppearance, 0)), 0); Mesh untintedMesh = getUntintedRetexturedMesh(new MeshCacheKey(item_state, new TransformCacheKey(nbtAppearance, 0)), 0);
QuadEmitter quad_emitter = context.getEmitter(); QuadEmitter quad_emitter = context.getEmitter();
if(tint == 0xFFFFFFFF) { if(tint == 0xFFFFFFFF) {
@ -145,7 +149,22 @@ public abstract class RetexturingBakedModel extends ForwardingBakedModel {
protected Mesh createUntintedRetexturedMesh(MeshCacheKey key, long seed) { protected Mesh createUntintedRetexturedMesh(MeshCacheKey key, long seed) {
RetexturingTransformer transformer = retextured_transforms.computeIfAbsent(key.transform, (k) -> new RetexturingTransformer(k.appearance, seed)); RetexturingTransformer transformer = retextured_transforms.computeIfAbsent(key.transform, (k) -> new RetexturingTransformer(k.appearance, seed));
return MeshTransformUtil.pretransformMesh(getBaseMesh(key.state), transformer); return pretransformMesh(getBaseMesh(key.state), transformer);
}
private static Mesh pretransformMesh(Mesh mesh, RetexturingTransformer transform) {
MeshBuilder builder = ReFramedClient.HELPER.getFabricRenderer().meshBuilder();
QuadEmitter emitter = builder.getEmitter();
mesh.forEach(quad -> {
int i = -1;
do {
emitter.copyFrom(quad);
i = transform.transform(emitter, i);
} while (i > 0);
});
return builder.build();
} }
public class RetexturingTransformer { public class RetexturingTransformer {
@ -174,7 +193,7 @@ public abstract class RetexturingBakedModel extends ForwardingBakedModel {
properties.sprite(), properties.sprite(),
MutableQuadView.BAKE_NORMALIZED MutableQuadView.BAKE_NORMALIZED
| properties.flags() | properties.flags()
| (uvlock ? MutableQuadView.BAKE_LOCK_UV : 0) | (uv_lock ? MutableQuadView.BAKE_LOCK_UV : 0)
); );
quad.tag(i+1); quad.tag(i+1);
quad.emit(); quad.emit();
@ -188,7 +207,7 @@ public abstract class RetexturingBakedModel extends ForwardingBakedModel {
// apply new quad shape // apply new quad shape
quad.material(ta.getRenderMaterial(ao)); quad.material(ta.getRenderMaterial(ao));
bounds.intersection(origin_bounds, direction.getAxis()).apply(quad, origin_bounds); bounds.intersection(origin_bounds, direction.getAxis()).apply(quad, origin_bounds);
quad.spriteBake( // TODO check if the flags are usefull because it seems to be braking the functioning of it quad.spriteBake( // seems to work without the flags and break with it
properties.sprite(), properties.sprite(),
MutableQuadView.BAKE_NORMALIZED MutableQuadView.BAKE_NORMALIZED
| MutableQuadView.BAKE_LOCK_UV | MutableQuadView.BAKE_LOCK_UV

View File

@ -1,7 +1,6 @@
package fr.adrien1106.reframed.client.model; package fr.adrien1106.reframed.client.model;
import fr.adrien1106.reframed.client.ReFramedClient; import fr.adrien1106.reframed.client.ReFramedClient;
import fr.adrien1106.reframed.util.ThemeableBlockEntity;
import net.fabricmc.fabric.api.renderer.v1.Renderer; import net.fabricmc.fabric.api.renderer.v1.Renderer;
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial; import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh; import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
@ -24,8 +23,8 @@ import java.util.function.Function;
public class UnbakedAutoRetexturedModel extends UnbakedRetexturedModel { public class UnbakedAutoRetexturedModel extends UnbakedRetexturedModel {
public UnbakedAutoRetexturedModel(Identifier parent, Function<ThemeableBlockEntity, BlockState> state_getter) { public UnbakedAutoRetexturedModel(Identifier parent) {
super(parent, state_getter); super(parent);
item_state = Blocks.AIR.getDefaultState(); item_state = Blocks.AIR.getDefaultState();
} }
@ -35,7 +34,7 @@ public class UnbakedAutoRetexturedModel extends UnbakedRetexturedModel {
return new RetexturingBakedModel( return new RetexturingBakedModel(
baker.bake(parent, bake_settings), baker.bake(parent, bake_settings),
ReFramedClient.HELPER.getCamoApperanceManager(texture_getter), ReFramedClient.HELPER.getCamoApperanceManager(texture_getter),
state_getter, theme_index,
bake_settings, bake_settings,
item_state, item_state,
ao ao

View File

@ -19,15 +19,16 @@ public class UnbakedDoubleRetexturedModel implements UnbakedModel {
protected final UnbakedModel model_1; protected final UnbakedModel model_1;
protected final UnbakedModel model_2; protected final UnbakedModel model_2;
public UnbakedDoubleRetexturedModel(UnbakedModel model_1, UnbakedModel model_2) { public UnbakedDoubleRetexturedModel(UnbakedRetexturedModel model_1, UnbakedRetexturedModel model_2) {
this.model_1 = model_1; this.model_1 = model_1;
this.model_2 = model_2; this.model_2 = model_2;
model_2.setThemeIndex(2);
} }
@Override @Override
public Collection<Identifier> getModelDependencies() { public Collection<Identifier> getModelDependencies() {
return List.of(model_1.getModelDependencies().iterator().next(), model_2.getModelDependencies().iterator().next()); return List.of(((List<Identifier>) model_1.getModelDependencies()).get(0), ((List<Identifier>) model_2.getModelDependencies()).get(0));
} }
@Override @Override

View File

@ -2,7 +2,6 @@ package fr.adrien1106.reframed.client.model;
import fr.adrien1106.reframed.ReFramed; import fr.adrien1106.reframed.ReFramed;
import fr.adrien1106.reframed.client.ReFramedClient; import fr.adrien1106.reframed.client.ReFramedClient;
import fr.adrien1106.reframed.util.ThemeableBlockEntity;
import net.fabricmc.fabric.api.renderer.v1.Renderer; import net.fabricmc.fabric.api.renderer.v1.Renderer;
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial; import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh; import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
@ -25,25 +24,25 @@ import java.util.Objects;
import java.util.function.Function; import java.util.function.Function;
public class UnbakedJsonRetexturedModel extends UnbakedRetexturedModel { public class UnbakedJsonRetexturedModel extends UnbakedRetexturedModel {
public UnbakedJsonRetexturedModel(Identifier parent, Function<ThemeableBlockEntity, BlockState> state_getter) { public UnbakedJsonRetexturedModel(Identifier parent) {
super(parent, state_getter); super(parent);
} }
@Nullable @Nullable
@Override @Override
public BakedModel bake(Baker baker, Function<SpriteIdentifier, Sprite> spriteLookup, ModelBakeSettings bake_settings, Identifier identifier) { public BakedModel bake(Baker baker, Function<SpriteIdentifier, Sprite> spriteLookup, ModelBakeSettings bake_settings, Identifier identifier) {
Direction[] DIRECTIONS = RetexturingBakedModel.DIRECTIONS; Direction[] directions = Direction.values();
Sprite[] specialSprites = new Sprite[DIRECTIONS.length]; Sprite[] sprites = new Sprite[directions.length];
for(int i = 0; i < DIRECTIONS.length; i++) { for(int i = 0; i < directions.length; i++) {
SpriteIdentifier id = new SpriteIdentifier(PlayerScreenHandler.BLOCK_ATLAS_TEXTURE, ReFramed.id("reframed_special/" + DIRECTIONS[i].getName())); 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 + " !"); sprites[i] = Objects.requireNonNull(spriteLookup.apply(id), () -> "Couldn't find sprite " + id + " !");
} }
return new RetexturingBakedModel( return new RetexturingBakedModel(
baker.bake(parent, bake_settings), baker.bake(parent, bake_settings),
ReFramedClient.HELPER.getCamoApperanceManager(spriteLookup), ReFramedClient.HELPER.getCamoApperanceManager(spriteLookup),
state_getter, theme_index,
bake_settings, bake_settings,
item_state, item_state,
ao ao
@ -61,9 +60,9 @@ public class UnbakedJsonRetexturedModel extends UnbakedRetexturedModel {
emitter.fromVanilla(quad, mat, cullFace); emitter.fromVanilla(quad, mat, cullFace);
QuadUvBounds bounds = QuadUvBounds.read(emitter); QuadUvBounds bounds = QuadUvBounds.read(emitter);
for(int i = 0; i < specialSprites.length; i++) { for(int i = 0; i < sprites.length; i++) {
if(bounds.displaysSprite(specialSprites[i])) { if(bounds.displaysSprite(sprites[i])) {
bounds.normalizeUv(emitter, specialSprites[i]); bounds.normalizeUv(emitter, sprites[i]);
emitter.tag(i + 1); emitter.tag(i + 1);
break; break;
} }

View File

@ -1,6 +1,5 @@
package fr.adrien1106.reframed.client.model; package fr.adrien1106.reframed.client.model;
import fr.adrien1106.reframed.util.ThemeableBlockEntity;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.client.render.model.UnbakedModel; import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
@ -12,14 +11,17 @@ import java.util.function.Function;
public abstract class UnbakedRetexturedModel implements UnbakedModel { public abstract class UnbakedRetexturedModel implements UnbakedModel {
protected final Identifier parent; protected final Identifier parent;
protected final Function<ThemeableBlockEntity, BlockState> state_getter;
protected int theme_index = 1;
protected BlockState item_state; protected BlockState item_state;
protected boolean ao = true; protected boolean ao = true;
public UnbakedRetexturedModel(Identifier parent, Function<ThemeableBlockEntity, BlockState> state_getter) { public UnbakedRetexturedModel(Identifier parent) {
this.parent = parent; this.parent = parent;
this.state_getter = state_getter; }
public void setThemeIndex(int theme_index) {
this.theme_index = theme_index;
} }
@Override @Override

View File

@ -44,29 +44,34 @@ public class CamoAppearanceManager {
materialsWithAo.put(blend, finder.ambientOcclusion(TriState.DEFAULT).find()); //not "true" since that *forces* AO, i just want to *allow* AO materialsWithAo.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); Sprite sprite = spriteLookup.apply(DEFAULT_SPRITE_MAIN);
if(defaultSprite == null) throw new IllegalStateException("Couldn't locate " + DEFAULT_SPRITE_ID + " !"); if(sprite == null) throw new IllegalStateException("Couldn't locate " + DEFAULT_SPRITE_MAIN + " !");
this.defaultAppearance = new SingleSpriteAppearance(defaultSprite, materialsWithoutAo.get(BlendMode.CUTOUT), serialNumber.getAndIncrement()); this.default_appearance = new SingleSpriteAppearance(sprite, materialsWithoutAo.get(BlendMode.CUTOUT), serial_number.getAndIncrement());
Sprite barrier = spriteLookup.apply(BARRIER_SPRITE_ID); sprite = spriteLookup.apply(DEFAULT_SPRITE_SECONDARY);
if(barrier == null) barrier = defaultSprite; //eh if(sprite == null) throw new IllegalStateException("Couldn't locate " + DEFAULT_SPRITE_MAIN + " !");
this.barrierItemAppearance = new SingleSpriteAppearance(barrier, materialsWithoutAo.get(BlendMode.CUTOUT), serialNumber.getAndIncrement()); this.accent_appearance = new SingleSpriteAppearance(sprite, materialsWithoutAo.get(BlendMode.CUTOUT), serial_number.getAndIncrement());
sprite = spriteLookup.apply(BARRIER_SPRITE_ID);
this.barrierItemAppearance = new SingleSpriteAppearance(sprite, materialsWithoutAo.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 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 final CamoAppearance barrierItemAppearance;
private final ConcurrentHashMap<BlockState, CamoAppearance> appearanceCache = new ConcurrentHashMap<>(); //Mutable, append-only cache private final ConcurrentHashMap<BlockState, CamoAppearance> appearanceCache = new ConcurrentHashMap<>(); //Mutable, append-only cache
private final AtomicInteger serialNumber = new AtomicInteger(0); //Mutable private final AtomicInteger serial_number = new AtomicInteger(0); //Mutable
private final EnumMap<BlendMode, RenderMaterial> materialsWithAo = new EnumMap<>(BlendMode.class); private final EnumMap<BlendMode, RenderMaterial> materialsWithAo = new EnumMap<>(BlendMode.class);
private final EnumMap<BlendMode, RenderMaterial> materialsWithoutAo = new EnumMap<>(BlendMode.class); //Immutable contents private final EnumMap<BlendMode, RenderMaterial> materialsWithoutAo = new EnumMap<>(BlendMode.class); //Immutable contents
public CamoAppearance getDefaultAppearance() { public CamoAppearance getDefaultAppearance(int appearance) {
return defaultAppearance; return appearance == 2 ? accent_appearance: default_appearance;
} }
public CamoAppearance getCamoAppearance(BlockRenderView world, BlockState state, BlockPos pos) { public CamoAppearance getCamoAppearance(BlockRenderView world, BlockState state, BlockPos pos) {
@ -96,7 +101,7 @@ public class CamoAppearanceManager {
getAppearance(model), getAppearance(model),
getCachedMaterial(state, true), getCachedMaterial(state, true),
getCachedMaterial(state, false), getCachedMaterial(state, false),
serialNumber.getAndIncrement() serial_number.getAndIncrement()
); );
} }
List<Weighted.Present<Appearance>> appearances = weighted_model.getModels().stream() List<Weighted.Present<Appearance>> appearances = weighted_model.getModels().stream()
@ -107,7 +112,7 @@ public class CamoAppearanceManager {
appearances, appearances,
getCachedMaterial(state, true), getCachedMaterial(state, true),
getCachedMaterial(state, false), getCachedMaterial(state, false),
serialNumber.getAndIncrement() serial_number.getAndIncrement()
); );
} }
@ -124,7 +129,7 @@ public class CamoAppearanceManager {
Arrays.stream(Direction.values()).forEach(direction -> { Arrays.stream(Direction.values()).forEach(direction -> {
List<BakedQuad> quads = model.getQuads(null, direction, random); List<BakedQuad> quads = model.getQuads(null, direction, random);
if(quads.isEmpty()) { // add default appearance if none present if(quads.isEmpty()) { // add default appearance if none present
sprites.put(direction, defaultAppearance.getSprites(direction, 0)); sprites.put(direction, default_appearance.getSprites(direction, 0));
return; return;
} }

View File

@ -13,7 +13,7 @@ public class Generator implements DataGeneratorEntrypoint {
/** /**
* missing DOOR, IRON_DOOR, CANDLE * 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); public static List<Block> BLOCKS = List.of(CUBE, STAIRS, SLAB, DOUBLE_SLAB, POST, FENCE, FENCE_GATE, TRAPDOOR, IRON_TRAPDOOR, PRESSURE_PLATE, BUTTON, LEVER, WALL, CARPET, PANE);
@Override @Override
public void onInitializeDataGenerator(FabricDataGenerator data_generator) { public void onInitializeDataGenerator(FabricDataGenerator data_generator) {
FabricDataGenerator.Pack myPack = data_generator.createPack(); FabricDataGenerator.Pack myPack = data_generator.createPack();

View File

@ -16,12 +16,13 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(WrappedGetter.class) @Mixin(WrappedGetter.class)
public class AthenaWrappedGetterMixin { public class AthenaWrappedGetterMixin {
// TODO return only the state that might be of interest
@Shadow @Final private BlockRenderView getter; @Shadow @Final private BlockRenderView getter;
@Inject(method = "getBlockState", at = @At(value = "HEAD"), cancellable = true) @Inject(method = "getBlockState", at = @At(value = "HEAD"), cancellable = true)
private void getCamoState(BlockPos pos, CallbackInfoReturnable<BlockState> cir) { private void getCamoState(BlockPos pos, CallbackInfoReturnable<BlockState> cir) {
if (!(getter.getBlockEntity(pos) instanceof ThemeableBlockEntity framed_entity)) return; if (!(getter.getBlockEntity(pos) instanceof ThemeableBlockEntity framed_entity)) return;
cir.setReturnValue(framed_entity.getFirstTheme()); cir.setReturnValue(framed_entity.getTheme(1)); // TODO theme
} }
@Redirect(method = "getAppearance(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/math/Direction;)" + @Redirect(method = "getAppearance(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/util/math/Direction;)" +
@ -30,7 +31,7 @@ public class AthenaWrappedGetterMixin {
"getBlockState(Lnet/minecraft/util/math/BlockPos;)" + "getBlockState(Lnet/minecraft/util/math/BlockPos;)" +
"Lnet/minecraft/block/BlockState;")) "Lnet/minecraft/block/BlockState;"))
private BlockState appearanceCamoState(BlockRenderView world, BlockPos pos) { private BlockState appearanceCamoState(BlockRenderView world, BlockPos pos) {
if (world.getBlockEntity(pos) instanceof ThemeableBlockEntity framed_entity) return framed_entity.getFirstTheme(); if (world.getBlockEntity(pos) instanceof ThemeableBlockEntity framed_entity) return framed_entity.getTheme(1); // TODO theme
return world.getBlockState(pos); return world.getBlockState(pos);
} }
@ -39,7 +40,7 @@ public class AthenaWrappedGetterMixin {
"getBlockState(Lnet/minecraft/util/math/BlockPos;)" + "getBlockState(Lnet/minecraft/util/math/BlockPos;)" +
"Lnet/minecraft/block/BlockState;")) "Lnet/minecraft/block/BlockState;"))
private BlockState queryCamoState(BlockRenderView world, BlockPos pos) { private BlockState queryCamoState(BlockRenderView world, BlockPos pos) {
if (world.getBlockEntity(pos) instanceof ThemeableBlockEntity framed_entity) return framed_entity.getFirstTheme(); if (world.getBlockEntity(pos) instanceof ThemeableBlockEntity framed_entity) return framed_entity.getTheme(1); // TODO theme
return world.getBlockState(pos); return world.getBlockState(pos);
} }
} }

View File

@ -1,5 +1,6 @@
package fr.adrien1106.reframed.mixin.particles; package fr.adrien1106.reframed.mixin.particles;
import fr.adrien1106.reframed.block.ReFramedBlock;
import fr.adrien1106.reframed.util.ThemeableBlockEntity; import fr.adrien1106.reframed.util.ThemeableBlockEntity;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
@ -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) { 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; AccessorParticle a = (AccessorParticle) this;
if(a.getRandom().nextBoolean() && clientWorld.getBlockEntity(pos) instanceof ThemeableBlockEntity themeable) { if(a.getRandom().nextBoolean()
BlockState theme = themeable.getFirstTheme(); && clientWorld.getBlockEntity(pos) instanceof ThemeableBlockEntity themeable
&& state.getBlock() instanceof ReFramedBlock block
) {
BlockState theme = themeable.getTheme(block.getTopThemeIndex(state));
if(theme == null || theme.isAir()) return; if(theme == null || theme.isAir()) return;
Sprite replacement = MinecraftClient.getInstance().getBlockRenderManager().getModels().getModelParticleSprite(theme); Sprite replacement = MinecraftClient.getInstance().getBlockRenderManager().getModels().getModelParticleSprite(theme);

View File

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

View File

@ -1,5 +1,7 @@
package fr.adrien1106.reframed.mixin.particles; package fr.adrien1106.reframed.mixin.particles;
import com.llamalad7.mixinextras.sugar.Local;
import fr.adrien1106.reframed.block.ReFramedBlock;
import fr.adrien1106.reframed.util.ThemeableBlockEntity; import fr.adrien1106.reframed.util.ThemeableBlockEntity;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
@ -7,33 +9,25 @@ import net.minecraft.entity.LivingEntity;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World; import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Mixin; 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.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyArg; import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(LivingEntity.class) @Mixin(LivingEntity.class)
public class MixinLivingEntity { 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( @ModifyArg(
method = "fall", method = "fall",
at = @At(value = "INVOKE", target = "Lnet/minecraft/particle/BlockStateParticleEffect;<init>(Lnet/minecraft/particle/ParticleType;Lnet/minecraft/block/BlockState;)V") 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(); World world = ((Entity) (Object) this).getWorld();
if(lastFallCheckPos != null && world.getBlockEntity(lastFallCheckPos) instanceof ThemeableBlockEntity themeable) { if(world.getBlockEntity(land_pos) instanceof ThemeableBlockEntity themeable
BlockState theme = themeable.getFirstTheme(); && state.getBlock() instanceof ReFramedBlock block) {
if(!theme.isAir()) return theme; BlockState theme = themeable.getTheme(block.getTopThemeIndex(state));
if(!theme.isAir()) return theme;
} }
return origState; return state;
} }
} }

View File

@ -1,25 +1,29 @@
package fr.adrien1106.reframed.mixin; package fr.adrien1106.reframed.mixin.render;
import com.llamalad7.mixinextras.sugar.Local; import com.llamalad7.mixinextras.sugar.Local;
import fr.adrien1106.reframed.block.ReFramedBlock;
import fr.adrien1106.reframed.util.IBlockRenderInfoMixin;
import fr.adrien1106.reframed.util.ThemeableBlockEntity; import fr.adrien1106.reframed.util.ThemeableBlockEntity;
import net.fabricmc.fabric.impl.client.indigo.renderer.render.BlockRenderInfo; import net.fabricmc.fabric.impl.client.indigo.renderer.render.BlockRenderInfo;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.entity.BlockEntity; import net.minecraft.block.entity.BlockEntity;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction; import net.minecraft.util.math.Direction;
import net.minecraft.world.BlockRenderView; import net.minecraft.world.BlockRenderView;
import net.minecraft.world.BlockView;
import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow; 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.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyArg; import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(BlockRenderInfo.class) @Mixin(BlockRenderInfo.class)
public abstract class BlockRenderInfoMixin { public abstract class BlockRenderInfoMixin implements IBlockRenderInfoMixin {
@Shadow public BlockPos blockPos; @Shadow public BlockPos blockPos;
@ -29,21 +33,38 @@ public abstract class BlockRenderInfoMixin {
@Shadow @Final private BlockPos.Mutable searchPos; @Shadow @Final private BlockPos.Mutable searchPos;
@Shadow public abstract void prepareForBlock(BlockState blockState, BlockPos blockPos, boolean modelAo);
@Unique
private int theme_index = 1;
@ModifyArg(method = "prepareForBlock", @ModifyArg(method = "prepareForBlock",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/RenderLayers;" + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/RenderLayers;" +
"getBlockLayer(Lnet/minecraft/block/BlockState;)Lnet/minecraft/client/render/RenderLayer;")) "getBlockLayer(Lnet/minecraft/block/BlockState;)Lnet/minecraft/client/render/RenderLayer;"))
public BlockState prepareCamoLayer(BlockState state, @Local(argsOnly = true) BlockPos pos) { public BlockState prepareCamoLayer(BlockState state, @Local(argsOnly = true) BlockPos pos) {
BlockEntity block_entity = MinecraftClient.getInstance().world.getBlockEntity(pos); BlockEntity block_entity = MinecraftClient.getInstance().world.getBlockEntity(pos);
if (!(block_entity instanceof ThemeableBlockEntity frame_entity)) return state; if (!(block_entity instanceof ThemeableBlockEntity frame_entity)) return state;
return frame_entity.getFirstTheme(); return frame_entity.getTheme(theme_index);
} }
@Inject(method = "shouldDrawFace", @Inject(method = "shouldDrawFace",
at = @At(value = "INVOKE_ASSIGN", target = "Lnet/minecraft/util/math/Direction;getId()I"), at = @At(value = "INVOKE_ASSIGN", target = "Lnet/minecraft/util/math/Direction;getId()I"),
cancellable = true) cancellable = true)
private void shouldDrawCamoFace(Direction face, CallbackInfoReturnable<Boolean> cir) { private void shouldDrawCamoFace(Direction face, CallbackInfoReturnable<Boolean> cir) {
// early injection for camos themselves
BlockEntity block_entity = MinecraftClient.getInstance().world.getBlockEntity(blockPos); BlockEntity block_entity = MinecraftClient.getInstance().world.getBlockEntity(blockPos);
if (!(block_entity instanceof ThemeableBlockEntity)) return; if (!(block_entity instanceof ThemeableBlockEntity)) return;
cir.setReturnValue(Block.shouldDrawSide(blockState, blockView, blockPos, face, searchPos.set(blockPos, face))); cir.setReturnValue(ReFramedBlock.shouldDrawSide(blockState, blockView, blockPos, face, searchPos.set(blockPos, face), 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 ReFramedBlock.shouldDrawSide(state, world, pos, side, other_pos, theme_index);
}
@Override
public void prepareForBlock(BlockState state, BlockPos pos, boolean ao, int theme_index) {
this.theme_index = theme_index;
prepareForBlock(state, pos, ao);
} }
} }

View File

@ -0,0 +1,25 @@
package fr.adrien1106.reframed.mixin.render;
import fr.adrien1106.reframed.util.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,41 @@
package fr.adrien1106.reframed.mixin.render;
import fr.adrien1106.reframed.client.model.MultiRetexturableModel;
import fr.adrien1106.reframed.util.IBlockRenderInfoMixin;
import fr.adrien1106.reframed.util.IMultipartBakedModelMixin;
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",
shift = At.Shift.AFTER
), 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<BakedModel> models = retexturing_model.models();
int i = 0;
for (BakedModel bakedModel : models) {
i++;
aoCalc.clear();
((IBlockRenderInfoMixin) blockInfo).prepareForBlock(state, pos, bakedModel.useAmbientOcclusion(), i);
bakedModel.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,9 @@
package fr.adrien1106.reframed.util;
import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos;
public interface IBlockRenderInfoMixin {
void prepareForBlock(BlockState state, BlockPos pos, boolean ao, int theme_index);
}

View File

@ -0,0 +1,9 @@
package fr.adrien1106.reframed.util;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.model.BakedModel;
public interface IMultipartBakedModelMixin {
BakedModel getModel(BlockState state);
}

View File

@ -28,8 +28,10 @@ import org.jetbrains.annotations.Nullable;
//For an example of how to use this class, have a look at TemplateBlock. //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. //Basically there are several methods that would like to modify the return value of something.
public class ReFramedInteractionUtil { public class ReFramedInteractionUtil {
@Deprecated // TODO remove
public static final BooleanProperty LIGHT = BooleanProperty.of("frame_light"); public static final BooleanProperty LIGHT = BooleanProperty.of("frame_light");
@Deprecated // TODO remove
public static StateManager.Builder<Block, BlockState> appendProperties(StateManager.Builder<Block, BlockState> builder) { public static StateManager.Builder<Block, BlockState> appendProperties(StateManager.Builder<Block, BlockState> builder) {
return builder.add(LIGHT); return builder.add(LIGHT);
} }
@ -37,24 +39,31 @@ public class ReFramedInteractionUtil {
//Use this to obtain a Block.Settings that'll make your Template act like the ones in the mod. //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.) //(To complete the look, don't forget to tag your blocks with mineable/axe.)
private static final AbstractBlock.ContextPredicate NOPE = (blah, blahdey, blahh) -> false; private static final AbstractBlock.ContextPredicate NOPE = (blah, blahdey, blahh) -> false;
// TODO Find better place
public static AbstractBlock.Settings configureSettings(AbstractBlock.Settings s) { public static AbstractBlock.Settings configureSettings(AbstractBlock.Settings s) {
return s.luminance(ReFramedInteractionUtil::luminance).nonOpaque().sounds(BlockSoundGroup.WOOD).hardness(0.2f).suffocates(NOPE).blockVision(NOPE); return s.luminance(ReFramedInteractionUtil::luminance).nonOpaque().sounds(BlockSoundGroup.WOOD).hardness(0.2f).suffocates(NOPE).blockVision(NOPE);
} }
// TODO Find better place
//And if you don't have a Block.Settings to copy off of. //And if you don't have a Block.Settings to copy off of.
public static AbstractBlock.Settings makeSettings() { public static AbstractBlock.Settings makeSettings() {
return configureSettings(AbstractBlock.Settings.create()); return configureSettings(AbstractBlock.Settings.create());
} }
@Deprecated // TODO remove
public static BlockState setDefaultStates(BlockState in) { public static BlockState setDefaultStates(BlockState in) {
if(in.contains(LIGHT)) in = in.with(LIGHT, false); if(in.contains(LIGHT)) in = in.with(LIGHT, false);
return in; return in;
} }
@Deprecated // TODO remove
public static @Nullable BlockState modifyPlacementState(@Nullable BlockState in, ItemPlacementContext ctx) { public static @Nullable BlockState modifyPlacementState(@Nullable BlockState in, ItemPlacementContext ctx) {
return ReFramedEntity.weirdNbtLightLevelStuff(in, ctx.getStack()); return ReFramedEntity.getNbtLightLevel(in, ctx.getStack());
} }
@Deprecated // TODO remove
public static ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) { public static ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
if(!(world.getBlockEntity(pos) instanceof ReFramedEntity block_entity)) return ActionResult.PASS; if(!(world.getBlockEntity(pos) instanceof ReFramedEntity block_entity)) return ActionResult.PASS;
if(!player.canModifyBlocks() || !world.canPlayerModifyAt(player, pos)) return ActionResult.PASS; if(!player.canModifyBlocks() || !world.canPlayerModifyAt(player, pos)) return ActionResult.PASS;
@ -96,14 +105,14 @@ public class ReFramedInteractionUtil {
return ActionResult.SUCCESS; return ActionResult.SUCCESS;
} }
// Changing the theme TODO Move outside // Changing the theme
if(held.getItem() instanceof BlockItem block_item && block_entity.getFirstTheme().getBlock() == Blocks.AIR) { if(held.getItem() instanceof BlockItem block_item && block_entity.getTheme(1).getBlock() == Blocks.AIR) {
Block block = block_item.getBlock(); Block block = block_item.getBlock();
ItemPlacementContext ctx = new ItemPlacementContext(new ItemUsageContext(player, hand, hit)); ItemPlacementContext ctx = new ItemPlacementContext(new ItemUsageContext(player, hand, hit));
BlockState placementState = block.getPlacementState(ctx); BlockState placementState = block.getPlacementState(ctx);
if(placementState != null && Block.isShapeFullCube(placementState.getCollisionShape(world, pos)) && !(block instanceof BlockEntityProvider)) { if(placementState != null && Block.isShapeFullCube(placementState.getCollisionShape(world, pos)) && !(block instanceof BlockEntityProvider)) {
// TODO FOR SECOND // TODO FOR SECOND
if(!world.isClient) block_entity.setFirstTheme(placementState); if(!world.isClient) block_entity.setTheme(placementState, 1);
// check for default light emission // check for default light emission
if (placementState.getLuminance() > 0) if (placementState.getLuminance() > 0)
@ -126,6 +135,7 @@ public class ReFramedInteractionUtil {
return ActionResult.PASS; return ActionResult.PASS;
} }
@Deprecated
public static void onStateReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) { public static void onStateReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) {
if(!state.isOf(newState.getBlock()) && if(!state.isOf(newState.getBlock()) &&
world.getBlockEntity(pos) instanceof ReFramedEntity frame_entity && world.getBlockEntity(pos) instanceof ReFramedEntity frame_entity &&
@ -133,7 +143,7 @@ public class ReFramedInteractionUtil {
) { ) {
DefaultedList<ItemStack> drops = DefaultedList.of(); DefaultedList<ItemStack> drops = DefaultedList.of();
BlockState theme = frame_entity.getFirstTheme(); BlockState theme = frame_entity.getTheme(1);
if(theme.getBlock() != Blocks.AIR) drops.add(new ItemStack(theme.getBlock())); if(theme.getBlock() != Blocks.AIR) drops.add(new ItemStack(theme.getBlock()));
if(frame_entity.emitsRedstone() && theme.getWeakRedstonePower(world, pos, Direction.NORTH) == 0) if(frame_entity.emitsRedstone() && theme.getWeakRedstonePower(world, pos, Direction.NORTH) == 0)
@ -147,6 +157,7 @@ public class ReFramedInteractionUtil {
} }
} }
@Deprecated
public static void onPlaced(World world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { 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. //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. //I'm surprised this doesn't happen by default; the BlockEntityTag stuff is only done serverside.
@ -156,20 +167,24 @@ public class ReFramedInteractionUtil {
} }
} }
@Deprecated
//Returns "null" to signal "no opinion". Imagine it like an InteractionResult.PASS. //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) { 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; return view.getBlockEntity(pos) instanceof ReFramedEntity be && !be.isSolid() ? VoxelShapes.empty() : null;
} }
@Deprecated // TODO remove
public static boolean emitsRedstonePower(BlockState state) { public static boolean emitsRedstonePower(BlockState state) {
//return state.contains(REDSTONE) ? state.get(REDSTONE) : false; //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. 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.
} }
@Deprecated // TODO remove
public static int getWeakRedstonePower(BlockState state, BlockView view, BlockPos pos, Direction dir) { public static int getWeakRedstonePower(BlockState state, BlockView view, BlockPos pos, Direction dir) {
return view.getBlockEntity(pos) instanceof ReFramedEntity be && be.emitsRedstone() ? 15 : 0; return view.getBlockEntity(pos) instanceof ReFramedEntity be && be.emitsRedstone() ? 15 : 0;
} }
@Deprecated // TODO remove
public static int getStrongRedstonePower(BlockState state, BlockView view, BlockPos pos, Direction dir) { public static int getStrongRedstonePower(BlockState state, BlockView view, BlockPos pos, Direction dir) {
return view.getBlockEntity(pos) instanceof ReFramedEntity be && be.emitsRedstone() ? 15 : 0; return view.getBlockEntity(pos) instanceof ReFramedEntity be && be.emitsRedstone() ? 15 : 0;
} }

View File

@ -2,16 +2,12 @@ package fr.adrien1106.reframed.util;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import java.util.List;
public interface ThemeableBlockEntity { public interface ThemeableBlockEntity {
BlockState getFirstTheme(); BlockState getTheme(int i);
default BlockState getSecondTheme() { void setTheme(BlockState state, int i);
return getFirstTheme();
}
void setFirstTheme(BlockState state); List<BlockState> getThemes();
default void setSecondTheme(BlockState state) {
setFirstTheme(state);
}
} }

View File

@ -1,65 +0,0 @@
{
"multipart": [
{
"apply": {
"model": "reframed:slab_special",
"uvlock": true
},
"when": {
"facing": "down"
}
},
{
"apply": {
"model": "reframed:slab_special",
"uvlock": true,
"x": 180
},
"when": {
"facing": "up"
}
},
{
"apply": {
"model": "reframed:slab_special",
"uvlock": true,
"x": 270
},
"when": {
"facing": "north"
}
},
{
"apply": {
"model": "reframed:slab_special",
"uvlock": true,
"x": 90
},
"when": {
"facing": "south"
}
},
{
"apply": {
"model": "reframed:slab_special",
"uvlock": true,
"x": 90,
"y": 90
},
"when": {
"facing": "west"
}
},
{
"apply": {
"model": "reframed:slab_special",
"uvlock": true,
"x": 90,
"y": 270
},
"when": {
"facing": "east"
}
}
]
}

View File

@ -9,16 +9,18 @@
"particles.MixinLivingEntity" "particles.MixinLivingEntity"
], ],
"client": [ "client": [
"BlockMixin",
"BlockRenderInfoMixin",
"MinecraftAccessor", "MinecraftAccessor",
"compat.AthenaBakedModelMixin", "compat.AthenaBakedModelMixin",
"compat.AthenaWrappedGetterMixin",
"compat.AthenaConnectedBlockModelMixin", "compat.AthenaConnectedBlockModelMixin",
"compat.AthenaWrappedGetterMixin",
"model.WeightedBakedModelAccessor", "model.WeightedBakedModelAccessor",
"particles.AccessorParticle", "particles.AccessorParticle",
"particles.AccessorSpriteBillboardParticle", "particles.AccessorSpriteBillboardParticle",
"particles.MixinBlockDustParticle" "particles.MixinBlockDustParticle",
"render.BlockRenderInfoMixin",
"render.MultipartBakedModelMixin",
"render.TerrainRenderContextMixin",
"render.WorldRendererMixin"
], ],
"injectors": { "injectors": {
"defaultRequire": 1 "defaultRequire": 1