CC-Tweaked/projects/common/src/main/java/dan200/computercraft/shared/platform/PlatformHelper.java

388 lines
16 KiB
Java

// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.platform;
import com.google.gson.JsonObject;
import com.mojang.authlib.GameProfile;
import com.mojang.brigadier.arguments.ArgumentType;
import dan200.computercraft.api.network.wired.WiredElement;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.config.ConfigFile;
import dan200.computercraft.shared.network.MessageType;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.client.ClientNetworkContext;
import dan200.computercraft.shared.network.container.ContainerData;
import dan200.computercraft.shared.util.InventoryUtil;
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Registry;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.common.ClientCommonPacketListener;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.level.ServerPlayerGameMode;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import net.minecraft.tags.TagKey;
import net.minecraft.world.*;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.CraftingContainer;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Recipe;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import javax.annotation.Nullable;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
/**
* This extends {@linkplain dan200.computercraft.impl.PlatformHelper the API's loader abstraction layer}, adding
* additional methods used by the actual mod.
*/
public interface PlatformHelper extends dan200.computercraft.impl.PlatformHelper {
/**
* Get the current {@link PlatformHelper} instance.
*
* @return The current instance.
*/
static PlatformHelper get() {
return (PlatformHelper) dan200.computercraft.impl.PlatformHelper.get();
}
/**
* Check if we're running in a development environment.
*
* @return If we're running in a development environment.
*/
boolean isDevelopmentEnvironment();
/**
* Create a new config builder.
*
* @return The newly created config builder.
*/
ConfigFile.Builder createConfigBuilder();
/**
* Create a registration helper for a specific registry.
*
* @param registry The registry we'll add entries to.
* @param <T> The type of object stored in the registry.
* @return The registration helper.
*/
<T> RegistrationHelper<T> createRegistrationHelper(ResourceKey<Registry<T>> registry);
/**
* Determine if this resource should be loaded, based on platform-specific loot conditions.
* <p>
* This should only be called from the {@code apply} stage of a reload listener.
*
* @param object The root JSON object of this resource.
* @return If this resource should be loaded.
*/
boolean shouldLoadResource(JsonObject object);
/**
* Create a new block entity type which serves a particular block.
*
* @param factory The method which creates a new block entity with this type, typically the constructor.
* @param block The block this block entity exists on.
* @param <T> The type of block entity we're creating.
* @return The new block entity type.
*/
<T extends BlockEntity> BlockEntityType<T> createBlockEntityType(BiFunction<BlockPos, BlockState, T> factory, Block block);
/**
* Register a new argument type.
*
* @param klass The argument type we're registering.
* @param info The argument type info.
* @param <A> The argument type.
* @param <T> The argument type template.
* @param <I> Argument type info
* @return The registered argument type.
*/
<A extends ArgumentType<?>, T extends ArgumentTypeInfo.Template<A>, I extends ArgumentTypeInfo<A, T>> I registerArgumentTypeInfo(Class<A> klass, I info);
/**
* Create a menu type which sends additional data when opened.
*
* @param reader Parse the additional container data into a usable type.
* @param factory The factory to create the new menu.
* @param <C> The menu/container than we open.
* @param <T> The data that we send to the client.
* @return The menu type for this container.
*/
<C extends AbstractContainerMenu, T extends ContainerData> MenuType<C> createMenuType(Function<FriendlyByteBuf, T> reader, ContainerData.Factory<C, T> factory);
/**
* Open a container using a specific {@link ContainerData}.
*
* @param player The player to open the menu for.
* @param owner The underlying menu provider.
* @param menu The menu data.
*/
void openMenu(Player player, MenuProvider owner, ContainerData menu);
/**
* Create a new {@link MessageType}.
*
* @param <T> The type of this message.
* @param id The id for this message type.
* @param reader The function which reads the packet from a buffer. Should be the inverse to {@link NetworkMessage#write(FriendlyByteBuf)}.
* @return The new {@link MessageType} instance.
*/
<T extends NetworkMessage<?>> MessageType<T> createMessageType(ResourceLocation id, FriendlyByteBuf.Reader<T> reader);
/**
* Convert a clientbound {@link NetworkMessage} to a Minecraft {@link Packet}.
*
* @param message The messsge to convert.
* @return The converted message.
*/
Packet<ClientCommonPacketListener> createPacket(NetworkMessage<ClientNetworkContext> message);
/**
* Invalidate components on a block enitty.
*
* @param owner The block entity whose components should be invalidated.
*/
default void invalidateComponent(BlockEntity owner) {
}
/**
* Create a {@link ComponentAccess} for surrounding peripherals.
*
* @param owner The block entity requesting surrounding peripherals.
* @param invalidate The function to call when a neighbouring peripheral potentially changes. This <em>MAY NOT</em>
* include all changes, and so block updates should still be listened to.
* @return The peripheral component access.
*/
ComponentAccess<IPeripheral> createPeripheralAccess(BlockEntity owner, Consumer<Direction> invalidate);
/**
* Create a {@link ComponentAccess} for surrounding wired nodes.
*
* @param owner The block entity requesting surrounding wired elements.
* @param invalidate The function to call when a neighbouring wired node potentially changes. This <em>MAY NOT</em>
* include all changes, and so block updates should still be listened to.
* @return The peripheral component access.
*/
ComponentAccess<WiredElement> createWiredElementAccess(BlockEntity owner, Consumer<Direction> invalidate);
/**
* Determine if there is a wired element in the given direction. This is equivalent to
* {@code createWiredElementAt(x -> {}).get(level, pos, dir) != null}, but is intended for when we don't need the
* cache.
*
* @param level The current level.
* @param pos The <em>current</em> block's position.
* @param direction The direction to check in.
* @return Whether there is a wired element in the given direction.
*/
boolean hasWiredElementIn(Level level, BlockPos pos, Direction direction);
/**
* Wrap a vanilla Minecraft {@link Container} into a {@link ContainerTransfer}.
*
* @param container The container to wrap.
* @return The container transfer.
*/
ContainerTransfer.Slotted wrapContainer(Container container);
/**
* Get access to a {@link ContainerTransfer} for a given position. This should look up blocks, then fall back to
* {@link InventoryUtil#getEntityContainer(ServerLevel, BlockPos, Direction)}
*
* @param level The current level.
* @param pos The current position.
* @param side The side of the block we're viewing the inventory from. Equivalent to the direction argument for
* {@link WorldlyContainer}.
* @return The container, or {@code null} if none exists.
*/
@Nullable
ContainerTransfer getContainer(ServerLevel level, BlockPos pos, Direction side);
/**
* Get the {@link RecipeIngredients} for this loader.
*
* @return The loader-specific recipe ingredients.
*/
RecipeIngredients getRecipeIngredients();
/**
* Get a list of tags representing each Minecraft dye. This should follow the same order as {@linkplain DyeColor
* Minecraft's dyes}, starting with white and ending with black.
*
* @return A list of tags.
*/
List<TagKey<Item>> getDyeTags();
/**
* Get the amount of fuel an item provides.
*
* @param stack The item to burn.
* @return The amount of fuel it provides.
*/
int getBurnTime(ItemStack stack);
/**
* Create a builder for a new creative tab.
*
* @return The creative tab builder.
*/
CreativeModeTab.Builder newCreativeModeTab();
/**
* Get the "container" item to be returned after crafting. For instance, crafting with a lava bucket should return
* an empty bucket.
*
* @param stack The original item.
* @return The "remainder" item. May be {@link ItemStack#EMPTY}.
*/
ItemStack getCraftingRemainingItem(ItemStack stack);
/**
* A more general version of {@link #getCraftingRemainingItem(ItemStack)} which gets all remaining items for a
* recipe.
*
* @param player The player performing the crafting.
* @param recipe The recipe currently doing the crafting.
* @param container The crafting container.
* @return A list of items to return to the player after crafting.
*/
List<ItemStack> getRecipeRemainingItems(ServerPlayer player, Recipe<CraftingContainer> recipe, CraftingContainer container);
/**
* Fire an event after crafting has occurred.
*
* @param player The player performing the crafting.
* @param container The current crafting container.
* @param stack The resulting stack from crafting.
*/
void onItemCrafted(ServerPlayer player, CraftingContainer container, ItemStack stack);
/**
* Check whether we should notify neighbours in a particular direction.
*
* @param level The current level.
* @param pos The position of the current block.
* @param block The block which is performing the notification, should be equal to {@code level.getBlockState(pos)}.
* @param direction The direction we're notifying in.
* @return {@code true} if neighbours should be notified or {@code false} otherwise.
*/
boolean onNotifyNeighbour(Level level, BlockPos pos, BlockState block, Direction direction);
/**
* Create a new fake player.
*
* @param level The level the player should be created in.
* @param profile The user this player should mimic.
* @return The newly constructed fake player.
*/
ServerPlayer createFakePlayer(ServerLevel level, GameProfile profile);
/**
* Determine if a player is not a real player.
*
* @param player The player to check.
* @return Whether this player is fake.
*/
default boolean isFakePlayer(ServerPlayer player) {
// Any subclass of ServerPlayer (i.e. Forge's FakePlayer) is assumed to be a fake.
return player.connection == null || player.getClass() != ServerPlayer.class;
}
/**
* Get the distance a player can reach.
*
* @param player The player who is reaching.
* @return The distance (in blocks) that a player can reach.
*/
default double getReachDistance(Player player) {
return Player.getPickRange(player.isCreative());
}
/**
* Check if this item is a tool and has some secondary usage.
* <p>
* In practice, this only checks if a tool is a hoe or shovel. We don't want to include things like axes,
*
* @param stack The stack to check.
* @return Whether this tool has a secondary usage.
*/
boolean hasToolUsage(ItemStack stack);
/**
* Check if an entity can be attacked according to platform-specific events.
*
* @param player The player who is attacking.
* @param entity The entity we're attacking.
* @return If this entity can be attacked.
* @see Player#attack(Entity)
*/
InteractionResult canAttackEntity(ServerPlayer player, Entity entity);
/**
* Interact with an entity, for instance feeding cows.
* <p>
* Implementations should follow Minecraft behaviour - we try {@link Entity#interactAt(Player, Vec3, InteractionHand)}
* and then {@link Player#interactOn(Entity, InteractionHand)}. Loader-specific hooks should also be called.
*
* @param player The player which is interacting with the entity.
* @param entity The entity we're interacting with.
* @param hitPos The position our ray trace hit the entity. This is a position in-world, unlike
* {@link Entity#interactAt(Player, Vec3, InteractionHand)} which is relative to the entity.
* @return Whether any interaction occurred.
* @see Entity#interactAt(Player, Vec3, InteractionHand)
* @see Player#interactOn(Entity, InteractionHand)
* @see ServerGamePacketListenerImpl#handleInteract
*/
boolean interactWithEntity(ServerPlayer player, Entity entity, Vec3 hitPos);
/**
* Place an item against a block.
* <p>
* Implementations should largely mirror {@link ServerPlayerGameMode#useItemOn(ServerPlayer, Level, ItemStack, InteractionHand, BlockHitResult)}
* (including any loader-specific modifications), except the call to {@link BlockState#use(Level, Player, InteractionHand, BlockHitResult)}
* should only be evaluated when {@code canUseBlock} evaluates to true.
*
* @param player The player which is placing this item.
* @param stack The item to place.
* @param hit The collision with the block we're placing against.
* @param canUseBlock Test whether the block should be interacted with first.
* @return Whether any interaction occurred.
* @see ServerPlayerGameMode#useItemOn(ServerPlayer, Level, ItemStack, InteractionHand, BlockHitResult)
*/
InteractionResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit, Predicate<BlockState> canUseBlock);
/**
* Whether {@link net.minecraft.network.chat.ClickEvent.Action#RUN_COMMAND} can be used to run client commands.
*
* @return Whether client commands can be triggered from chat components.
*/
default boolean canClickRunClientCommand() {
return true;
}
}