Add LandPathNodeTypesRegistry (#2437)

* Added LandPathNodeTypesRegistry.

* Added test for LandPathNodeTypesRegistry.

* Added block state to PathNodeTypeProvider.getPathNodeType.

* Made LandPathNodeTypesRegistry final, removed unnecessary NotNull.

* Changed putIfAbsent with put to align with other registries.

* Cleanup.

Co-authored-by: Technici4n <13494793+Technici4n@users.noreply.github.com>

* Removed unused import.

* Added possibility to specify the node type of block if the block is found in a neighbor position, improved documentation.

* Merged mixins cleanup.

* Changed CAPTURE_FAILEXCEPTION into CAPTURE_FAILHARD

Co-authored-by: Juuxel <6596629+Juuxel@users.noreply.github.com>

* Specified some nullables, changed 2 parameter names.

* Added missing content registries tests, changed NODE_TYPES map to IdentityHashMap, improved docs.

* Moved PathNodeTypeProvider inside LandPathNodeTypesRegistry.

Co-authored-by: Technici4n <13494793+Technici4n@users.noreply.github.com>
Co-authored-by: Juuxel <6596629+Juuxel@users.noreply.github.com>
This commit is contained in:
Salvatore Peluso 2022-09-11 15:14:45 +02:00 committed by GitHub
parent 1cc24b1b0e
commit 6a999b8eb9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 209 additions and 0 deletions

View file

@ -0,0 +1,135 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.registry;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Objects;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.entity.ai.pathing.PathNodeType;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.BlockView;
/**
* A registry to associate specific node types to blocks.
* Specifying a node type for a block will change the way an entity recognizes the block when trying to pathfind.
* For example, you can specify that a block is dangerous and should be avoided by entities.
* This works only for entities that move on air and land.
*/
public final class LandPathNodeTypesRegistry {
private static final Logger LOGGER = LoggerFactory.getLogger(LandPathNodeTypesRegistry.class);
private static final Map<Block, PathNodeTypeProvider> NODE_TYPES = new IdentityHashMap<>();
private LandPathNodeTypesRegistry() {
}
/**
* Registers a {@link PathNodeType} for the specified block.
* This will override the default block behavior.
* For example, you can make a safe block as dangerous and vice-versa.
* Duplicated registrations for the same block will replace the previous registration.
*
* @param block Block to register.
* @param nodeType {@link PathNodeType} to associate with the block.
* (Pass {@code null} to not specify a node type and use the default behavior)
* @param nodeTypeIfNeighbor {@link PathNodeType} to associate to the block, if is a neighbor block in the path.
* (Pass {@code null} to not specify a node type and use the default behavior)
*/
public static void register(Block block, @Nullable PathNodeType nodeType, @Nullable PathNodeType nodeTypeIfNeighbor) {
Objects.requireNonNull(block, "Block cannot be null!");
// Registers a provider that always returns the specified node type.
register(block, (state, world, pos, neighbor) -> neighbor ? nodeTypeIfNeighbor : nodeType);
}
/**
* Registers a {@link PathNodeTypeProvider} for the specified block.
* This will override the default block behavior.
* For example, you can make a safe block as dangerous and vice-versa.
* Duplicated registrations for the same block will replace the previous registrations.
*
* @param block Block to register.
* @param provider {@link PathNodeTypeProvider} to associate with the block.
*/
public static void register(Block block, PathNodeTypeProvider provider) {
Objects.requireNonNull(block, "Block cannot be null!");
Objects.requireNonNull(provider, "PathNodeTypeProvider cannot be null!");
// Registers the provider.
PathNodeTypeProvider old = NODE_TYPES.put(block, provider);
if (old != null) {
LOGGER.debug("Replaced PathNodeType provider for the block {}", block);
}
}
/**
* Gets the custom {@link PathNodeType} registered for the specified block at the specified position.
*
* <p>If no custom {@link PathNodeType} is registered for the block, it returns {@code null}.
* You cannot use this method to retrieve vanilla block node types.
*
* @param state Current block state.
* @param world Current world.
* @param pos Current position.
* @param neighbor Specifies if the block is not a directly targeted block, but a neighbor block in the path.
* @return the custom {@link PathNodeType} registered for the specified block at the specified position.
*/
@Nullable
public static PathNodeType getPathNodeType(BlockState state, BlockView world, BlockPos pos, boolean neighbor) {
Objects.requireNonNull(state, "BlockState cannot be null!");
Objects.requireNonNull(world, "BlockView cannot be null!");
Objects.requireNonNull(pos, "BlockPos cannot be null!");
// Gets the node type for the block at the specified position.
PathNodeTypeProvider provider = NODE_TYPES.get(state.getBlock());
return provider != null ? provider.getPathNodeType(state, world, pos, neighbor) : null;
}
/**
* A functional interface that provides the {@link PathNodeType}, given the block state and position.
*/
@FunctionalInterface
public interface PathNodeTypeProvider {
/**
* Gets the {@link PathNodeType} for the specified block at the specified position.
*
* <p>You can specify what to return if the block is a direct target of an entity path,
* or a neighbor block that the entity will find in the path.
*
* <p>For example, for a cactus-like block you should specify {@link PathNodeType#DAMAGE_CACTUS} if the block
* is a direct target ({@code neighbor == false}) to specify that an entity should not pass through or above
* the block because it will cause damage, and {@link PathNodeType#DANGER_CACTUS} if the cactus will be found
* as a neighbor block in the entity path ({@code neighbor == true}) to specify that the entity should not get
* close to the block because is dangerous.
*
* @param state Current block state.
* @param world Current world.
* @param pos Current position.
* @param neighbor Specifies if the block is not a directly targeted block, but a neighbor block in the path.
* @return the custom {@link PathNodeType} registered for the specified block at the specified position.
*/
@Nullable
PathNodeType getPathNodeType(BlockState state, BlockView world, BlockPos pos, boolean neighbor);
}
}

View file

@ -0,0 +1,58 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.content.registry;
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.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import net.minecraft.block.BlockState;
import net.minecraft.entity.ai.pathing.LandPathNodeMaker;
import net.minecraft.entity.ai.pathing.PathNodeType;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.BlockView;
import net.fabricmc.fabric.api.registry.LandPathNodeTypesRegistry;
@Mixin(LandPathNodeMaker.class)
public class LandPathNodeMakerMixin {
/**
* Overrides the node type for the specified position in a path.
*/
@Inject(method = "getCommonNodeType", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/BlockView;getBlockState(Lnet/minecraft/util/math/BlockPos;)Lnet/minecraft/block/BlockState;", shift = At.Shift.BY, by = 2), locals = LocalCapture.CAPTURE_FAILHARD, cancellable = true)
private static void getCommonNodeType(BlockView world, BlockPos pos, CallbackInfoReturnable<PathNodeType> cir, BlockState state) {
PathNodeType nodeType = LandPathNodeTypesRegistry.getPathNodeType(state, world, pos, false);
if (nodeType != null) {
cir.setReturnValue(nodeType);
}
}
/**
* Overrides the node type for the specified position, if the position is found as neighbor block in a path.
*/
@Inject(method = "getNodeTypeFromNeighbors", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/BlockView;getBlockState(Lnet/minecraft/util/math/BlockPos;)Lnet/minecraft/block/BlockState;", shift = At.Shift.BY, by = 2), locals = LocalCapture.CAPTURE_FAILHARD, cancellable = true)
private static void getNodeTypeFromNeighbors(BlockView world, BlockPos.Mutable pos, PathNodeType nodeType, CallbackInfoReturnable<PathNodeType> cir, int i, int j, int k, int l, int m, int n, BlockState state) {
PathNodeType neighborNodeType = LandPathNodeTypesRegistry.getPathNodeType(state, world, pos, true);
if (neighborNodeType != null) {
cir.setReturnValue(neighborNodeType);
}
}
}

View file

@ -9,6 +9,7 @@
"GiveGiftsToHeroTaskAccessor",
"HoeItemAccessor",
"HoneycombItemMixin",
"LandPathNodeMakerMixin",
"AbstractFurnaceBlockEntityMixin",
"FireBlockMixin",
"OxidizableMixin",

View file

@ -23,10 +23,12 @@ import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.entity.ai.pathing.PathNodeType;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.HoeItem;
import net.minecraft.item.Items;
import net.minecraft.tag.BlockTags;
import net.minecraft.tag.ItemTags;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.Identifier;
@ -38,8 +40,11 @@ import net.minecraft.world.World;
import net.minecraft.world.event.GameEvent;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.registry.CompostingChanceRegistry;
import net.fabricmc.fabric.api.registry.FlammableBlockRegistry;
import net.fabricmc.fabric.api.registry.FlattenableBlockRegistry;
import net.fabricmc.fabric.api.registry.FuelRegistry;
import net.fabricmc.fabric.api.registry.LandPathNodeTypesRegistry;
import net.fabricmc.fabric.api.registry.OxidizableBlocksRegistry;
import net.fabricmc.fabric.api.registry.SculkSensorFrequencyRegistry;
import net.fabricmc.fabric.api.registry.StrippableBlockRegistry;
@ -56,8 +61,13 @@ public final class ContentRegistryTest implements ModInitializer {
@Override
public void onInitialize() {
// Expected behavior:
// - obsidian is now compostable
// - diamond block is now flammable
// - sand is now flammable
// - red wool is flattenable to yellow wool
// - obsidian is now fuel
// - all items with the tag 'minecraft:dirt' are now fuel
// - dead bush is now considered as a dangerous block like sweet berry bushes (all entities except foxes should avoid it)
// - quartz pillars are strippable to hay blocks
// - green wool is tillable to lime wool
// - copper ore, iron ore, gold ore, and diamond ore can be waxed into their deepslate variants and scraped back again
@ -67,8 +77,13 @@ public final class ContentRegistryTest implements ModInitializer {
// - assign a loot table to the nitwit villager type
// - right-clicking a 'test_event' block will emit a 'test_event' game event, which will have a sculk sensor frequency of 2
CompostingChanceRegistry.INSTANCE.add(Items.OBSIDIAN, 0.5F);
FlammableBlockRegistry.getDefaultInstance().add(Blocks.DIAMOND_BLOCK, 4, 4);
FlammableBlockRegistry.getDefaultInstance().add(BlockTags.SAND, 4, 4);
FlattenableBlockRegistry.register(Blocks.RED_WOOL, Blocks.YELLOW_WOOL.getDefaultState());
FuelRegistry.INSTANCE.add(Items.OBSIDIAN, 60);
FuelRegistry.INSTANCE.add(ItemTags.DIRT, 120);
LandPathNodeTypesRegistry.register(Blocks.DEAD_BUSH, PathNodeType.DAMAGE_OTHER, PathNodeType.DANGER_OTHER);
StrippableBlockRegistry.register(Blocks.QUARTZ_PILLAR, Blocks.HAY_BLOCK);
// assert that StrippableBlockRegistry throws when the blocks don't have 'axis'