Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,32 @@
# Branch Information

This branch specifically tackles the PedestalBlockRenderer desync issues and its boilerplate by making
use of [Cardinal Components API](https://ladysnake.org/wiki/cardinal-components-api/).

The [Implemented Inventory Interface](./src/main/java/net/kaupenjoe/tutorialmod/block/entity/ImplementedInventory.java)
has been switched out with a direct `Sided Inventory` implementation in
the [BlockEntity](./src/main/java/net/kaupenjoe/tutorialmod/block/entity/custom/PedestalBlockEntity.java) to support
component syncing. If necessary, this can get cleaned up more.

The [Pedestal Component](./src/main/java/net/kaupenjoe/tutorialmod/cca/component/SyncedPedestalComponent.java) also
takes
care of the rotation for the client side rendering now.

For more information check out [Cardinal Component API's wiki](https://ladysnake.org/wiki/cardinal-components-api/).

---

<a href="https://www.youtube.com/playlist?list=PLKGarocXCE1H_HxOYihQMq0mlpqiUJj4L" target="_blank">
<p align="center">
<img src="https://kaupenjoe.net/files/General/Minecraft/Modding/Tutorials/fabric-tutorial-image-1.png" alt="Logo" width="1000"/>
</p></a>

# Fabric Modding Tutorials For Minecraft 1.21.X
# Fabric Modding Tutorials For Minecraft 1.21.X

This is the GitHub Repository for Kaupenjoe's Fabric Modding Tutorials For Minecraft 1.21.X

The Individual Tutorials are seperated into Branches for ease of access.
The Individual Tutorials are seperated into Branches for ease of access.

Watch the Tutorials here: <a href="https://www.youtube.com/playlist?list=PLKGarocXCE1H_HxOYihQMq0mlpqiUJj4L" target="_blank">YouTube Playlist</a>
Watch the Tutorials
here: <a href="https://www.youtube.com/playlist?list=PLKGarocXCE1H_HxOYihQMq0mlpqiUJj4L" target="_blank">YouTube
Playlist</a>
17 changes: 11 additions & 6 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ base {
}

repositories {
// Add repositories to retrieve artifacts from in here.
// You should only use this when depending on other mods because
// Loom adds the essential maven repositories to download Minecraft and libraries from automatically.
// See https://docs.gradle.org/current/userguide/declaring_repositories.html
// for more information about repositories.
// Cardinal Components API (CCA)
maven {
name = "Ladysnake Mods"
url = 'https://maven.ladysnake.org/releases'
}
}

fabricApi {
Expand All @@ -30,7 +30,12 @@ dependencies {

// Fabric API. This is technically optional, but you probably want it anyway.
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"


// Cardinal Components API (CCA)
// for even more providers, check out the wiki: https://ladysnake.org/wiki/cardinal-components-api/
modImplementation "org.ladysnake.cardinal-components-api:cardinal-components-base:${project.cca_version}"
modImplementation "org.ladysnake.cardinal-components-api:cardinal-components-block:${project.cca_version}"
// to automatically include the api in the mod jar, check out the wiki too...
}

processResources {
Expand Down
5 changes: 4 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,7 @@ maven_group=net.kaupenjoe.tutorialmod
archives_base_name=tutorialmod

# Fabric API
fabric_version=0.103.0+1.21.1
fabric_version=0.103.0+1.21.1

# Dependencies
cca_version = 6.1.2
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ protected BlockRenderType getRenderType(BlockState state) {

@Override
protected void onStateReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) {
if(state.getBlock() != newState.getBlock()) {
if (state.getBlock() != newState.getBlock()) {
BlockEntity blockEntity = world.getBlockEntity(pos);
if(blockEntity instanceof PedestalBlockEntity) {
if (blockEntity instanceof PedestalBlockEntity) {
ItemScatterer.spawn(world, pos, ((PedestalBlockEntity) blockEntity));
world.updateComparators(pos, this);
}
Expand All @@ -63,25 +63,26 @@ protected void onStateReplaced(BlockState state, World world, BlockPos pos, Bloc
@Override
protected ItemActionResult onUseWithItem(ItemStack stack, BlockState state, World world, BlockPos pos,
PlayerEntity player, Hand hand, BlockHitResult hit) {
if(world.getBlockEntity(pos) instanceof PedestalBlockEntity pedestalBlockEntity) {
if(pedestalBlockEntity.isEmpty() && !stack.isEmpty()) {
pedestalBlockEntity.setStack(0, stack.copyWithCount(1));
world.playSound(player, pos, SoundEvents.ENTITY_ITEM_PICKUP, SoundCategory.BLOCKS, 1f, 2f);
stack.decrement(1);

pedestalBlockEntity.markDirty();
world.updateListeners(pos, state, state, 0);
} else if(stack.isEmpty() && !player.isSneaking()) {
ItemStack stackOnPedestal = pedestalBlockEntity.getStack(0);
player.setStackInHand(Hand.MAIN_HAND, stackOnPedestal);
world.playSound(player, pos, SoundEvents.ENTITY_ITEM_PICKUP, SoundCategory.BLOCKS, 1f, 1f);
pedestalBlockEntity.clear();
if (!(world.getBlockEntity(pos) instanceof PedestalBlockEntity blockEntity)) {
return super.onUseWithItem(stack, state, world, pos, player, hand, hit);
}
if (blockEntity.isEmpty() && !stack.isEmpty()) {
blockEntity.setStack(0, stack.copyWithCount(1));
world.playSound(player, pos, SoundEvents.ENTITY_ITEM_PICKUP, SoundCategory.BLOCKS, 1f, 2f);
stack.decrement(1);

pedestalBlockEntity.markDirty();
world.updateListeners(pos, state, state, 0);
}
world.updateListeners(pos, state, state, 0);
return ItemActionResult.SUCCESS;
}
if (blockEntity.getSyncedComponent().hasContent() && stack.isEmpty() && !player.isSneaking()) {
ItemStack stackOnPedestal = blockEntity.getStack(0);
player.setStackInHand(Hand.MAIN_HAND, stackOnPedestal);
world.playSound(player, pos, SoundEvents.ENTITY_ITEM_PICKUP, SoundCategory.BLOCKS, 1f, 1f);
blockEntity.clear();

return ItemActionResult.SUCCESS;
world.updateListeners(pos, state, state, 0);
return ItemActionResult.SUCCESS;
}
return ItemActionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION;
}
}
Original file line number Diff line number Diff line change
@@ -1,61 +1,97 @@
package net.kaupenjoe.tutorialmod.block.entity.custom;

import net.kaupenjoe.tutorialmod.block.entity.ImplementedInventory;
import net.kaupenjoe.tutorialmod.block.entity.ModBlockEntities;
import net.kaupenjoe.tutorialmod.cca.component.SyncedPedestalComponent;
import net.minecraft.block.BlockState;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.inventory.Inventories;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.inventory.SidedInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.listener.ClientPlayPacketListener;
import net.minecraft.network.packet.Packet;
import net.minecraft.network.packet.s2c.play.BlockEntityUpdateS2CPacket;
import net.minecraft.registry.RegistryWrapper;
import net.minecraft.util.collection.DefaultedList;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import org.jetbrains.annotations.Nullable;

public class PedestalBlockEntity extends BlockEntity implements ImplementedInventory {
private final DefaultedList<ItemStack> inventory = DefaultedList.ofSize(1, ItemStack.EMPTY);
private float rotation = 0;
public class PedestalBlockEntity extends BlockEntity implements SidedInventory {
private final SyncedPedestalComponent syncedComponent;

public PedestalBlockEntity(BlockPos pos, BlockState state) {
super(ModBlockEntities.PEDESTAL_BE, pos, state);
this.syncedComponent = SyncedPedestalComponent.get(this);
}

@Override
public DefaultedList<ItemStack> getItems() {
return inventory;
}

public float getRenderingRotation() {
rotation += 0.5f;
if(rotation >= 360) {
rotation = 0;
}
return rotation;
public SyncedPedestalComponent getSyncedComponent() {
return syncedComponent;
}

@Override
protected void writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) {
super.writeNbt(nbt, registryLookup);
Inventories.writeNbt(nbt, inventory, registryLookup);
}

@Override
protected void readNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) {
super.readNbt(nbt, registryLookup);
Inventories.readNbt(nbt, inventory, registryLookup);
}

@Nullable
@Override
public Packet<ClientPlayPacketListener> toUpdatePacket() {
return BlockEntityUpdateS2CPacket.create(this);
public int[] getAvailableSlots(Direction side) {
int[] result = new int[syncedComponent.getInventory().size()];
for (int i = 0; i < result.length; i++) {
result[i] = i;
}
return result;
}

@Override
public boolean canInsert(int slot, ItemStack stack, @Nullable Direction dir) {
return dir == null || dir.equals(Direction.UP);
}

@Override
public boolean canExtract(int slot, ItemStack stack, Direction dir) {
return dir == null || dir.equals(Direction.DOWN);
}

@Override
public int size() {
return syncedComponent.getInventory().size();
}

@Override
public boolean isEmpty() {
return !syncedComponent.hasContent();
}

@Override
public ItemStack getStack(int slot) {
return syncedComponent.getInventory().get(slot);
}

@Override
public ItemStack removeStack(int slot, int amount) {
return this.syncedComponent.remove(slot, amount, true);
}

@Override
public ItemStack removeStack(int slot) {
return this.syncedComponent.remove(slot, null, true);
}

@Override
public void setStack(int slot, ItemStack stack) {
this.syncedComponent.modifyInventory(itemStacks -> itemStacks.set(slot, stack), true);
}

@Override
public boolean canPlayerUse(PlayerEntity player) {
return true;
}

@Override
public NbtCompound toInitialChunkDataNbt(RegistryWrapper.WrapperLookup registryLookup) {
return createNbt(registryLookup);
public void clear() {
this.syncedComponent.modifyInventory(DefaultedList::clear, true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,33 @@
import net.minecraft.client.render.item.ItemRenderer;
import net.minecraft.client.render.model.json.ModelTransformationMode;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.RotationAxis;
import net.minecraft.world.LightType;
import net.minecraft.world.World;

public class PedestalBlockEntityRenderer implements BlockEntityRenderer<PedestalBlockEntity> {
@SuppressWarnings("unused")
public PedestalBlockEntityRenderer(BlockEntityRendererFactory.Context context) {

}

@Override
public void render(PedestalBlockEntity entity, float tickDelta, MatrixStack matrices,
public void render(PedestalBlockEntity blockEntity, float tickDelta, MatrixStack matrices,
VertexConsumerProvider vertexConsumers, int light, int overlay) {
if (!(blockEntity.getWorld() instanceof ClientWorld clientWorld)) return;
ItemRenderer itemRenderer = MinecraftClient.getInstance().getItemRenderer();
ItemStack stack = entity.getStack(0);
ItemStack stack = blockEntity.getStack(0);

matrices.push();
matrices.translate(0.5f, 1.15f, 0.5f);
matrices.scale(0.5f, 0.5f, 0.5f);
matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(entity.getRenderingRotation()));
matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(blockEntity.getSyncedComponent().getRenderingRotation(tickDelta)));

itemRenderer.renderItem(stack, ModelTransformationMode.GUI, getLightLevel(entity.getWorld(),
entity.getPos()), OverlayTexture.DEFAULT_UV, matrices, vertexConsumers, entity.getWorld(), 1);
itemRenderer.renderItem(stack, ModelTransformationMode.GUI, getLightLevel(clientWorld,
blockEntity.getPos()), OverlayTexture.DEFAULT_UV, matrices, vertexConsumers, blockEntity.getWorld(), 1);
matrices.pop();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package net.kaupenjoe.tutorialmod.cca;

import net.kaupenjoe.tutorialmod.block.entity.custom.PedestalBlockEntity;
import net.kaupenjoe.tutorialmod.cca.component.SyncedPedestalComponent;
import net.kaupenjoe.tutorialmod.cca.implementation.SyncedPedestalComponentImpl;
import org.ladysnake.cca.api.v3.block.BlockComponentFactoryRegistry;
import org.ladysnake.cca.api.v3.block.BlockComponentInitializer;
import org.ladysnake.cca.api.v3.component.ComponentKey;
import org.ladysnake.cca.api.v3.component.ComponentRegistry;

public class TutorialModCCAComponents implements BlockComponentInitializer {
public static final ComponentKey<SyncedPedestalComponent> SYNCED_BLOCK_INVENTORY =
ComponentRegistry.getOrCreate(SyncedPedestalComponent.IDENTIFIER, SyncedPedestalComponent.class);

@Override
public void registerBlockComponentFactories(BlockComponentFactoryRegistry registry) {
registry.registerFor(PedestalBlockEntity.class, SYNCED_BLOCK_INVENTORY, SyncedPedestalComponentImpl::new);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package net.kaupenjoe.tutorialmod.cca.component;

import net.kaupenjoe.tutorialmod.TutorialMod;
import net.kaupenjoe.tutorialmod.block.entity.custom.PedestalBlockEntity;
import net.kaupenjoe.tutorialmod.cca.TutorialModCCAComponents;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Identifier;
import net.minecraft.util.collection.DefaultedList;
import org.jetbrains.annotations.Nullable;
import org.ladysnake.cca.api.v3.component.Component;
import org.ladysnake.cca.api.v3.component.tick.ClientTickingComponent;

import java.util.function.Consumer;

public interface SyncedPedestalComponent extends Component, ClientTickingComponent {
Identifier IDENTIFIER = Identifier.of(TutorialMod.MOD_ID, "synced_pedestal");

static SyncedPedestalComponent get(PedestalBlockEntity blockEntity) {
return TutorialModCCAComponents.SYNCED_BLOCK_INVENTORY.get(blockEntity);
}

/**
* For proper Inventory modification, use {@link #modifyInventory(Consumer, boolean)}
*/
DefaultedList<ItemStack> getInventory();

void modifyInventory(Consumer<DefaultedList<ItemStack>> consumer, boolean shouldSync);

ItemStack remove(int slot, @Nullable Integer amount, boolean shouldSync);

default boolean hasContent() {
for (ItemStack stack : getInventory()) {
if (!stack.isEmpty()) {
return true;
}
}
return false;
}

float getRenderingRotation(float tickDelta);

void sync();
}
Loading