Custom Implementation
This option is only recommended if you need to implement the electricity system into an existing block entity class and you can't use one of the prebuilt classes, or you want full control of everything. Ensure you follow all the steps carefully to avoid implementing the methods and handling syncing incorrectly.
Implementing the Interface
You'll need to decide whether you are creating a block that will recieves electrical power, or provides electrical power to other blocks. A block that recieves power is considered a module and your block entity will need to implement IModuleNode
. A block that provides electrical power is known as a source and your block entity will need to implement ISourceNode
. An example of a module is the Computer, while a source is the Electricity Generator.
The interfaces are located in the package com.mrcrayfish.furniture.refurbished.electricity
Do not implement the parent interface IElectricityNode
as a block should only be either a module or a source, not both.
After adding the one of the interfaces to your block entity class, you will need to write your own implementation of the following methods:
getNodePosition()
- Simply return theBlockPos
of the block entity. UsuallyBlockEntity#worldPosition
.getNodeLevel()
- Simply return theLevel
of the block entity. UsuallyBlockEntity#level
.getNodeOwner()
- Simply return the block entity instance. Usuallythis
.isNodePowered()
- Return aboolean
representing the powered state. You could use a property from theBlockState
or a variable inside your custom block entity. You should be able to call this on both the client and server, so ensure the state is available on both sides.setNodePowered(boolean)
- Updates the powered state. If you are storing the state in aBlockState
property, just update that. If you use a variable inside your custom block entity, this is a good time to sync any changes to clients and mark the block entity for saving.getNodeConnections()
- Return aSet
ofConnection
objects. This should just be a simpleHashSet
. Connection management is handle automatically by the mod.
If you are inheriting IModuleNode
you will also need to implement the following:
isNodeReceivingPower()
- Return aboolean
representing if the block entity is receiving power from a source. This should just update a simpleboolean
variable inside your custom block entity. This variable should be update only bysetNodeReceivingPower(boolean)
below.setNodeReceivingPower(boolean)
- Marks the block entity as receiving power from an electricity source. This is called before block entities are ticked. This should just update a simpleboolean
variable inside your custom block entity. Syncing to client is optional.getPowerSources()
- Return aSet
that can holdBlockPos
. This set holds block positions of power sources (e.g. the Electricity Generator). This is used by the client to correctly render the powerable zone. You should just return a simpleHashSet
for this.
If you are inheriting ISourceNode
you will also need to implement the following:
isNodeOverloaded()
- Return aboolean
representing if the block entity is overloaded. This should just be a simpleboolean
variable inside your custom block entity.setNodeOverloaded(boolean)
- Updates the overloaded state. This should just be a simpleboolean
variable inside your custom block entity. The variable should be returned inisOverloaded()
.
Addtionally you will need to override hashCode()
if you implement either interface. This will simply improve the performance of the search algorithm used in the mod. This can be the hash of BlockEntity#worldPosition
.
Saving and Syncing the State
An imporant part to ensure your electricity block works correctly is:
- If you're creating a Module, saving the
powered
state to disk and syncing it to clients - If you're creating a Source, saving the
overloaded
state to disk - For both, saving the
connections
to disk and syncing them to clients
Saving the Powered State (Modules Only)
If you're creating an electricity block from IModuleNode
, the choice you made when implementing isNodePowered()
and setNodePowered(boolean)
affects whether or not you need to write additional code to save the powered state.
Block State Property
You do not need to add anything. This is the great thing about using a block state property, saving and syncing are handled already by the game. This is the recommend method even if you don't plan to use the property to affect the model of your block. You should obviously still consider if this option is appropriate for your full implementation.
Block Entity Variable
If you've gone with a simple boolean
variable in your block entity class, you will need to handle saving this yourself. This is however quite simple, you just need to append the following to your load(CompoundTag)
and saveAdditional(CompoundTag)
methods of your block entity.
- 1.20.4
- 1.20.1
// Your variable, may be named different
protected boolean powered;
@Override
public void load(CompoundTag tag) {
// ...
this.powered = tag.getBoolean("Powered"); // Load it
}
@Override
protected void saveAdditional(CompoundTag tag) {
// ...
tag.putBoolean("Powered", this.powered); // Save it
}
// Your variable, may be named different
protected boolean powered;
@Override
public void load(CompoundTag tag) {
// ...
this.powered = tag.getBoolean("Powered"); // Load it
}
@Override
protected void saveAdditional(CompoundTag tag) {
// ...
tag.putBoolean("Powered", this.powered); // Save it
}
Saving Everything Else
Conveniently this has all been made easy for you. In the load(CompoundTag)
and saveAdditional(CompoundTag)
methods of your block entity, you will need to call the methods readNodeNbt(CompoundTag)
and writeNodeNbt(CompoundTag)
. This will handle saving the connections
and if an ISourceNode
, the overloaded
state.
- 1.20.4
- 1.20.1
@Override
public void load(CompoundTag tag) {
// ...
this.readNodeNbt(tag);
}
@Override
protected void saveAdditional(CompoundTag tag) {
// ...
this.writeNodeNbt(tag);
}
@Override
public void load(CompoundTag tag) {
// ...
this.readNodeNbt(tag);
}
@Override
protected void saveAdditional(CompoundTag tag) {
// ...
this.writeNodeNbt(tag);
}
Syncing to Clients
Syncing the powered
state and connections
to the client is crucical for the interaction system of the Wrench and rendering. This can simply be achieved by overidding vanilla methods. You should only send what you need. By default, saveWithoutMetadata
will send everything written in saveAdditional(CompoundTag)
to clients. Sync updates are read by your load(CompoundTag)
method.
- 1.20.4
- 1.20.1
@Nullable
@Override
public ClientboundBlockEntityDataPacket getUpdatePacket() {
return ClientboundBlockEntityDataPacket.create(this);
}
@Override
public CompoundTag getUpdateTag() {
return this.saveWithoutMetadata(); // Heads up, this sends everything
}
@Nullable
@Override
public ClientboundBlockEntityDataPacket getUpdatePacket() {
return ClientboundBlockEntityDataPacket.create(this);
}
@Override
public CompoundTag getUpdateTag() {
return this.saveWithoutMetadata(); // Heads up, this sends everything
}
Register the Block Entity Renderer
In order to render the node and its connections, you'll need to register a block entity renderer for your block entity. This is also crucial for enabling interaction with the Wrench. You either have the option to register your block entity renderer with an existing class proivided by the mod, or if you already have a block entity renderer, you just need to call one method.
Option 1: Bind to Default Block Entity Renderer
MrCrayfish's Furniture Mod: Refurbished comes with a default block entity renderer for electricity blocks. Simply bind your block entity type with ElectricBlockEntityRenderer
and rendering and interactions will be handled for you. ElectricBlockEntityRenderer
accepts any block entity type that inherits BlockEntity
and IElectricityNode
. This option is only suitable if you don't plan to do any extra rendering. Refer to option B for more control.
- 1.20.4
- 1.20.1
- Forge
- Fabric
- NeoForge
import com.mrcrayfish.furniture.refurbished.client.renderer.blockentity.ElectricBlockEntityRenderer;
// Make sure to register this event on the mod event bus
public static void onRegisterRenderers(EntityRenderersEvent.RegisterRenderers event) {
// Replace <YourBlockEntityType> with the instance of the block entity type
event.registerBlockEntityRenderer(<YourBlockEntityType>, ElectricBlockEntityRenderer::new);
}
import com.mrcrayfish.furniture.refurbished.client.renderer.blockentity.ElectricBlockEntityRenderer;
// Call this in your client initialization. Replace <YourBlockEntityType> with the instance of the block entity type
BlockEntityRenderers.register(<YourBlockEntityType>, ElectricBlockEntityRenderer::new);
import com.mrcrayfish.furniture.refurbished.client.renderer.blockentity.ElectricBlockEntityRenderer;
// Make sure to register this event on the mod event bus
public static void onRegisterRenderers(EntityRenderersEvent.RegisterRenderers event) {
// Replace <YourBlockEntityType> with the instance of the block entity type
event.registerBlockEntityRenderer(<YourBlockEntityType>, ElectricBlockEntityRenderer::new);
}
- Forge
- Fabric
import com.mrcrayfish.furniture.refurbished.client.renderer.blockentity.ElectricBlockEntityRenderer;
// Make sure to register this event on the mod event bus
public static void onRegisterRenderers(EntityRenderersEvent.RegisterRenderers event) {
// Replace <YourBlockEntityType> with the instance of the block entity type
event.registerBlockEntityRenderer(<YourBlockEntityType>, ElectricBlockEntityRenderer::new);
}
import com.mrcrayfish.furniture.refurbished.client.renderer.blockentity.ElectricBlockEntityRenderer;
// Call this in your client initialization. Replace <YourBlockEntityType> with the instance of the block entity type
BlockEntityRenderers.register(<YourBlockEntityType>, ElectricBlockEntityRenderer::new);
Option 2: Modifying a Preexisting Block Entity Renderer
If you already have a block entity renderer, you just need to call drawNodeAndConnections
from ElectricBlockEntityRenderer
to enabled rendering and interactions. You can alternaltively extend ElectricBlockEntityRenderer
and call the super render
method if that works better for your case, and this would also avoid writing extra code to disable frustum culling (see next section).
- 1.20.4
- 1.20.1
import com.mrcrayfish.furniture.refurbished.client.renderer.blockentity.ElectricBlockEntityRenderer;
@Override
public void render(T entity, float partialTick, PoseStack stack, MultiBufferSource source, int light, int overlay) {
stack.pushPose();
// ...
stack.popPose();
// Call this last. Make sure you don't perform any transforms, wrap your rendering in push and pop.
ElectricBlockEntityRenderer.drawNodeAndConnections(entity, stack, source, overlay);
}
import com.mrcrayfish.furniture.refurbished.client.renderer.blockentity.ElectricBlockEntityRenderer;
@Override
public void render(T entity, float partialTick, PoseStack stack, MultiBufferSource source, int light, int overlay) {
stack.pushPose();
// ...
stack.popPose();
// Call this last. Make sure you don't perform any transforms, wrap your rendering in push and pop.
ElectricBlockEntityRenderer.drawNodeAndConnections(entity, stack, source, overlay);
}
Disable Frustum Culling
Frustum culling is a performance feature. It only allows blocks that are in the view of the camera to be drawn. This can be a problem for electricity blocks as there is a case where no electricity blocks are visible but the connections should be drawn. Since the Block Entity Renderer of the electricity block is responsible for drawing them, we need to add an exception to allow the blocks to be drawn even though they aren't in view.
You may not need to apply all these changes. This depends on if you extended one of the prebuilt classes or if you're using ElectricBlockEntityRenderer. Check if methods are overriden already and apply where needed.
- 1.20.4
- 1.20.1
- Forge
- Fabric
- NeoForge
import com.mrcrayfish.furniture.refurbished.Config;
@Override
public boolean shouldRenderOffScreen(T node) {
return true;
}
@Override
public int getViewDistance() {
// Use the config property from the mod for consistency
return Config.CLIENT.electricityViewDistance.get();
}
import com.mrcrayfish.furniture.refurbished.Config;
@Override
public AABB getRenderBoundingBox() { // From IForgeBlockEntity
// Use the config property from the mod for consistency
return new AABB(this.worldPosition).inflate(Config.CLIENT.electricityViewDistance.get());
}
import com.mrcrayfish.furniture.refurbished.Config;
@Override
public boolean shouldRenderOffScreen(T node) {
return true;
}
@Override
public int getViewDistance() {
// Use the config property from the mod for consistency
return Config.CLIENT.electricityViewDistance.get();
}
import com.mrcrayfish.furniture.refurbished.Config;
@Override
public boolean shouldRenderOffScreen(T node) {
return true;
}
@Override
public int getViewDistance() {
// Use the config property from the mod for consistency
return Config.CLIENT.electricityViewDistance.get();
}
@Override
public AABB getRenderBoundingBox(T node) {// From IBlockEntityRendererExtension
// Use the config property from the mod for consistency
return new AABB(node.getNodePosition()).inflate(Config.CLIENT.electricityViewDistance.get());
}
- Forge
- Fabric
import com.mrcrayfish.furniture.refurbished.Config;
@Override
public boolean shouldRenderOffScreen(T node) {
return true;
}
@Override
public int getViewDistance() {
return Config.CLIENT.electricityViewDistance.get();
}
import com.mrcrayfish.furniture.refurbished.Config;
@Override
public AABB getRenderBoundingBox() { // From IForgeBlockEntity
return new AABB(this.worldPosition).inflate(Config.CLIENT.electricityViewDistance.get());
}
import com.mrcrayfish.furniture.refurbished.Config;
@Override
public boolean shouldRenderOffScreen(T node) {
return true;
}
@Override
public int getViewDistance() {
return Config.CLIENT.electricityViewDistance.get();
}