fabric
fabric copied to clipboard
Add LandPathNodeTypesRegistry
Content
A registry for associations between blocks and path node types, for land entities.
This PR adds a new registry to the content registries module, this registry is useful when some mod adds a new block that cause damage and want to flag this block to be avoided by entities.
In vanilla this is done by getting the node type for the block while calculating the path for the entity (every block is a node).
To establish which node is a specific block is done in LandPathNodeMaker.getCommonNodeType(BlockView world, BlockPos pos)
.
This is basically a series of ifs, so normally you should add a mixin to this method with an if to give a PathNodeType
for your block.
This PR adds a way to do this common thing inside Fabric API.
I use this way in a mod that i'm developing.
public class LandPathNodeMaker extends PathNodeMaker {
[...]
protected static PathNodeType getCommonNodeType(BlockView world, BlockPos pos) {
BlockState blockState = world.getBlockState(pos);
// INJECTED HERE ANOTHER IF AND RETURN
Block block = blockState.getBlock();
Material material = blockState.getMaterial();
if (blockState.isAir()) {
return PathNodeType.OPEN;
} else if (!blockState.isIn(BlockTags.TRAPDOORS) && !blockState.isOf(Blocks.LILY_PAD) && !blockState.isOf(Blocks.BIG_DRIPLEAF)) {
if (blockState.isOf(Blocks.POWDER_SNOW)) {
return PathNodeType.POWDER_SNOW;
} else if (blockState.isOf(Blocks.CACTUS)) {
return PathNodeType.DAMAGE_CACTUS;
} else if (blockState.isOf(Blocks.SWEET_BERRY_BUSH)) {
return PathNodeType.DAMAGE_OTHER;
} else if (blockState.isOf(Blocks.HONEY_BLOCK)) {
return PathNodeType.STICKY_HONEY;
} else if (blockState.isOf(Blocks.COCOA)) {
return PathNodeType.COCOA;
} else {
FluidState fluidState = world.getFluidState(pos);
if (fluidState.isIn(FluidTags.LAVA)) {
return PathNodeType.LAVA;
} else if (inflictsFireDamage(blockState)) {
return PathNodeType.DAMAGE_FIRE;
} else if (DoorBlock.isWoodenDoor(blockState) && !(Boolean)blockState.get(DoorBlock.OPEN)) {
return PathNodeType.DOOR_WOOD_CLOSED;
} else if (block instanceof DoorBlock && material == Material.METAL && !(Boolean)blockState.get(DoorBlock.OPEN)) {
return PathNodeType.DOOR_IRON_CLOSED;
} else if (block instanceof DoorBlock && (Boolean)blockState.get(DoorBlock.OPEN)) {
return PathNodeType.DOOR_OPEN;
} else if (block instanceof AbstractRailBlock) {
return PathNodeType.RAIL;
} else if (block instanceof LeavesBlock) {
return PathNodeType.LEAVES;
} else if (blockState.isIn(BlockTags.FENCES) || blockState.isIn(BlockTags.WALLS) || block instanceof FenceGateBlock && !(Boolean)blockState.get(FenceGateBlock.OPEN)) {
return PathNodeType.FENCE;
} else if (!blockState.canPathfindThrough(world, pos, NavigationType.LAND)) {
return PathNodeType.BLOCKED;
} else {
return fluidState.isIn(FluidTags.WATER) ? PathNodeType.WATER : PathNodeType.OPEN;
}
}
} else {
return PathNodeType.TRAPDOOR;
}
}
[...]
}
Getting the node type
@Mixin(LandPathNodeMaker.class)
public class LandPathNodeMakerMixin {
/**
* Gets the node type for the specified position.
*/
@Inject(method = "getCommonNodeType", at = @At("HEAD"), cancellable = true)
private static void getCommonNodeType(@NotNull BlockView world, BlockPos pos, @NotNull CallbackInfoReturnable<PathNodeType> cir, BlockState state) {
PathNodeType nodeType = LandPathNodeTypesRegistry.getPathNodeType(state, world, pos);
if (nodeType != null) {
cir.setReturnValue(nodeType);
}
}
}
Why a provider?
I've added the registry as:
private static final HashMap<Block, PathNodeTypeProvider> NODE_TYPES = new HashMap<>();
PathNodeTypeProvider is an interface that has one method:
PathNodeType getPathNodeType(BlockState state, BlockView world, BlockPos pos);
I've chose this way instead to add directly PathNodeType because in a position there is always a block, but in case this block is "fluid-logged", in the same position there is the block and a fluid. With the provider, is possible to specify what to do if there is a "fluid-logged" block, so you can prioritize the block or the fluid.
Testing
For testing Dead Bush is added to the registry with PathNodeType.DAMAGE_OTHER
, so it is avoided by entities except foxes.
This is the same as sweet berry bushes.
This is implemented in the testmod.
https://user-images.githubusercontent.com/32025549/182221967-e24de458-94d0-47d3-8ca9-6e513223cdfb.mp4
Update
I've added the possibility to specify the node type of block if the block is found in a neighbor position in the path and improved docs.
Basically is a mixin to the method above:
LandPathNodeMaker.getNodeTypeFromNeighbors(BlockView world, BlockPos.Mutable pos, PathNodeType nodeType)
This is completed now.
Now the provider is changed as this:
PathNodeType getPathNodeType(BlockState state, BlockView world, BlockPos pos, boolean isNeighbor);
Please check if the documentation is written correctly in english or there are grammar errors.
Tecnical
public static PathNodeType getNodeTypeFromNeighbors(BlockView world, BlockPos.Mutable pos, PathNodeType nodeType) {
int i = pos.getX();
int j = pos.getY();
int k = pos.getZ();
for(int l = -1; l <= 1; ++l) {
for(int m = -1; m <= 1; ++m) {
for(int n = -1; n <= 1; ++n) {
if (l != 0 || n != 0) {
pos.set(i + l, j + m, k + n);
BlockState blockState = world.getBlockState(pos);
// INJECTED HERE ANOTHER IF AND RETURN
if (blockState.isOf(Blocks.CACTUS)) {
return PathNodeType.DANGER_CACTUS;
}
if (blockState.isOf(Blocks.SWEET_BERRY_BUSH)) {
return PathNodeType.DANGER_OTHER;
}
if (inflictsFireDamage(blockState)) {
return PathNodeType.DANGER_FIRE;
}
if (world.getFluidState(pos).isIn(FluidTags.WATER)) {
return PathNodeType.WATER_BORDER;
}
}
}
}
}
return nodeType;
}
I've added missing CompostingChanceRegistry
and FuelRegistry
to ContentRegistryTest
in the testmod.
Merged some minor edits into one single commit, then I've reverted to Objects.requireNotNull, and used IdentityHashMap, that is also used for the main tables in the internal vanilla main registries.
Postmortem: apparently Lithium @Overwrite
s the very method we mixin into. Whoops.
September Update (For new readers)
This is reworked in https://github.com/FabricMC/fabric/pull/2519 because of potential performance issues with the hashmap, some useless things, and problems with lithium.