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 theBlockPosof the block entity. UsuallyBlockEntity#worldPosition.getNodeLevel()- Simply return theLevelof the block entity. UsuallyBlockEntity#level.getNodeOwner()- Simply return the block entity instance. Usuallythis.isNodePowered()- Return abooleanrepresenting the powered state. You could use a property from theBlockStateor 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 aBlockStateproperty, 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 aSetofConnectionobjects. 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 abooleanrepresenting if the block entity is receiving power from a source. This should just update a simplebooleanvariable 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 simplebooleanvariable inside your custom block entity. Syncing to client is optional.getPowerSources()- Return aSetthat 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 simpleHashSetfor this.
If you are inheriting ISourceNode you will also need to implement the following:
isNodeOverloaded()- Return abooleanrepresenting if the block entity is overloaded. This should just be a simplebooleanvariable inside your custom block entity.setNodeOverloaded(boolean)- Updates the overloaded state. This should just be a simplebooleanvariable 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
poweredstate to disk and syncing it to clients - If you're creating a Source, saving the
overloadedstate to disk - For both, saving the
connectionsto 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();
}