/* * This file is part of ComputerCraft - http://www.computercraft.info * Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission. * Send enquiries to dratcliffe@gmail.com */ package dan200.computercraft.shared.platform; import com.google.auto.service.AutoService; import com.mojang.authlib.GameProfile; import com.mojang.brigadier.arguments.ArgumentType; import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.api.network.wired.WiredElement; import dan200.computercraft.api.node.wired.WiredElementLookup; import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.PeripheralLookup; import dan200.computercraft.mixin.ArgumentTypeInfosAccessor; import dan200.computercraft.shared.network.NetworkMessage; import dan200.computercraft.shared.network.client.ClientNetworkContext; import dan200.computercraft.shared.network.container.ContainerData; import dan200.computercraft.shared.peripheral.generic.GenericPeripheralProvider; import dan200.computercraft.shared.util.InventoryUtil; import net.fabricmc.fabric.api.event.player.AttackEntityCallback; import net.fabricmc.fabric.api.event.player.UseBlockCallback; import net.fabricmc.fabric.api.event.player.UseEntityCallback; import net.fabricmc.fabric.api.lookup.v1.block.BlockApiCache; import net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup; import net.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityTypeBuilder; import net.fabricmc.fabric.api.registry.FuelRegistry; import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerFactory; import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerType; import net.fabricmc.fabric.api.tag.convention.v1.ConventionalItemTags; import net.fabricmc.fabric.api.transfer.v1.item.InventoryStorage; import net.fabricmc.fabric.api.transfer.v1.item.ItemStorage; import net.minecraft.commands.synchronization.ArgumentTypeInfo; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.Registry; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerChunkCache; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.tags.TagKey; import net.minecraft.world.Container; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.MenuProvider; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Inventory; 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.*; import net.minecraft.world.item.context.UseOnContext; import net.minecraft.world.item.crafting.Ingredient; 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.level.chunk.LevelChunk; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.EntityHitResult; import net.minecraft.world.phys.Vec3; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; @AutoService(dan200.computercraft.impl.PlatformHelper.class) public class PlatformHelperImpl implements PlatformHelper { @SuppressWarnings("unchecked") private static Registry getRegistry(ResourceKey> id) { var registry = (Registry) BuiltInRegistries.REGISTRY.get(id.location()); if (registry == null) throw new IllegalArgumentException("Unknown registry " + id); return registry; } @Override public ResourceLocation getRegistryKey(ResourceKey> registry, T object) { var key = getRegistry(registry).getKey(object); if (key == null) throw new IllegalArgumentException(object + " was not registered in " + registry); return key; } @Override public T getRegistryObject(ResourceKey> registry, ResourceLocation id) { var value = getRegistry(registry).get(id); if (value == null) throw new IllegalArgumentException(id + " was not registered in " + registry); return value; } @Override public RegistryWrappers.RegistryWrapper wrap(ResourceKey> registry) { return new RegistryWrapperImpl<>(registry.location(), getRegistry(registry)); } @Override public RegistrationHelper createRegistrationHelper(ResourceKey> registry) { return new RegistrationHelperImpl<>(getRegistry(registry)); } @Nullable @Override public T tryGetRegistryObject(ResourceKey> registry, ResourceLocation id) { return getRegistry(registry).get(id); } @Override public BlockEntityType createBlockEntityType(BiFunction factory, Block block) { return FabricBlockEntityTypeBuilder.create(factory::apply).addBlock(block).build(); } @Override public , T extends ArgumentTypeInfo.Template, I extends ArgumentTypeInfo> I registerArgumentTypeInfo(Class klass, I info) { ArgumentTypeInfosAccessor.classMap().put(klass, info); return info; } @Override public MenuType createMenuType(Function reader, ContainerData.Factory factory) { return new ExtendedScreenHandlerType<>((id, player, data) -> factory.create(id, player, reader.apply(data))); } @Override public void openMenu(Player player, MenuProvider owner, ContainerData menu) { player.openMenu(new WrappedMenuProvider(owner, menu)); } @Override public void sendToPlayer(NetworkMessage message, ServerPlayer player) { player.connection.send(NetworkHandler.encodeClient(message)); } @Override public void sendToPlayers(NetworkMessage message, Collection players) { if (players.isEmpty()) return; var packet = NetworkHandler.encodeClient(message); for (var player : players) player.connection.send(packet); } @Override public void sendToAllPlayers(NetworkMessage message, MinecraftServer server) { server.getPlayerList().broadcastAll(NetworkHandler.encodeClient(message)); } @Override public void sendToAllAround(NetworkMessage message, ServerLevel level, Vec3 pos, float distance) { level.getServer().getPlayerList().broadcast(null, pos.x, pos.y, pos.z, distance, level.dimension(), NetworkHandler.encodeClient(message)); } @Override public void sendToAllTracking(NetworkMessage message, LevelChunk chunk) { var packet = NetworkHandler.encodeClient(message); for (var player : ((ServerChunkCache) chunk.getLevel().getChunkSource()).chunkMap.getPlayers(chunk.getPos(), false)) { player.connection.send(packet); } } @Override public ComponentAccess createPeripheralAccess(Consumer invalidate) { return new PeripheralAccessImpl(); } @Override public ComponentAccess createWiredElementAccess(Consumer invalidate) { return new ComponentAccessImpl<>(WiredElementLookup.get()); } @Override public boolean hasWiredElementIn(Level level, BlockPos pos, Direction direction) { return WiredElementLookup.get().find(level, pos.relative(direction), direction.getOpposite()) != null; } @Override public ContainerTransfer.Slotted wrapContainer(Container container) { return FabricContainerTransfer.of(InventoryStorage.of(container, null)); } @Override @SuppressWarnings("UnstableApiUsage") public @Nullable ContainerTransfer getContainer(ServerLevel level, BlockPos pos, Direction side) { var storage = ItemStorage.SIDED.find(level, pos, side); if (storage != null) return FabricContainerTransfer.of(storage); var entity = InventoryUtil.getEntityContainer(level, pos, side); return entity == null ? null : FabricContainerTransfer.of(InventoryStorage.of(entity, side)); } @Override public RecipeIngredients getRecipeIngredients() { return new RecipeIngredients( Ingredient.of(ConventionalItemTags.REDSTONE_DUSTS), Ingredient.of(Items.STRING), Ingredient.of(Items.LEATHER), Ingredient.of(Items.STONE), Ingredient.of(ConventionalItemTags.GLASS_PANES), Ingredient.of(ConventionalItemTags.GOLD_INGOTS), Ingredient.of(Items.GOLD_BLOCK), Ingredient.of(ConventionalItemTags.IRON_INGOTS), Ingredient.of(MoreConventionalTags.SKULLS), Ingredient.of(ConventionalItemTags.DYES), Ingredient.of(Items.ENDER_PEARL), Ingredient.of(MoreConventionalTags.WOODEN_CHESTS) ); } @Override public List> getDyeTags() { return List.of( ConventionalItemTags.WHITE_DYES, ConventionalItemTags.ORANGE_DYES, ConventionalItemTags.MAGENTA_DYES, ConventionalItemTags.LIGHT_BLUE_DYES, ConventionalItemTags.YELLOW_DYES, ConventionalItemTags.LIME_DYES, ConventionalItemTags.PINK_DYES, ConventionalItemTags.GRAY_DYES, ConventionalItemTags.LIGHT_GRAY_DYES, ConventionalItemTags.CYAN_DYES, ConventionalItemTags.PURPLE_DYES, ConventionalItemTags.BLUE_DYES, ConventionalItemTags.BROWN_DYES, ConventionalItemTags.GREEN_DYES, ConventionalItemTags.RED_DYES, ConventionalItemTags.BLACK_DYES ); } @Override public int getBurnTime(ItemStack stack) { @Nullable var fuel = FuelRegistry.INSTANCE.get(stack.getItem()); return fuel == null ? 0 : fuel; } @Nullable @Override public ResourceLocation getCreativeTabId(CreativeModeTab tab) { return tab.getId(); } @Override public ItemStack getCraftingRemainingItem(ItemStack stack) { return stack.getRecipeRemainder(); } @Override public List getRecipeRemainingItems(ServerPlayer player, Recipe recipe, CraftingContainer container) { return recipe.getRemainingItems(container); } @Override public void onItemCrafted(ServerPlayer player, CraftingContainer container, ItemStack stack) { } @Override public boolean onNotifyNeighbour(Level level, BlockPos pos, BlockState block, Direction direction) { return true; } @Override public ServerPlayer createFakePlayer(ServerLevel world, GameProfile name) { return FakePlayer.create(world, name); } @Override public boolean hasToolUsage(ItemStack stack) { var item = stack.getItem(); return item instanceof ShovelItem || stack.is(ConventionalItemTags.SHOVELS) || item instanceof HoeItem || stack.is(ConventionalItemTags.HOES); } @Override public InteractionResult canAttackEntity(ServerPlayer player, Entity entity) { return AttackEntityCallback.EVENT.invoker().interact(player, player.level, InteractionHand.MAIN_HAND, entity, null); } @Override public boolean interactWithEntity(ServerPlayer player, Entity entity, Vec3 hitPos) { return UseEntityCallback.EVENT.invoker().interact(player, entity.level, InteractionHand.MAIN_HAND, entity, new EntityHitResult(entity, hitPos)).consumesAction() || entity.interactAt(player, hitPos.subtract(entity.position()), InteractionHand.MAIN_HAND).consumesAction() || player.interactOn(entity, InteractionHand.MAIN_HAND).consumesAction(); } @Override public InteractionResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit) { var result = UseBlockCallback.EVENT.invoker().interact(player, player.level, InteractionHand.MAIN_HAND, hit); if (result != InteractionResult.PASS) return result; return stack.useOn(new UseOnContext(player, InteractionHand.MAIN_HAND, hit)); } private record RegistryWrapperImpl( ResourceLocation name, Registry registry ) implements RegistryWrappers.RegistryWrapper { @Override public int getId(T object) { var id = registry.getId(object); if (id == -1) throw new IllegalArgumentException(object + " was not registered in " + name); return id; } @Override public ResourceLocation getKey(T object) { var key = registry.getKey(object); if (key == null) throw new IllegalArgumentException(object + " was not registered in " + name); return key; } @Override public T get(ResourceLocation location) { var object = registry.get(location); if (object == null) throw new IllegalArgumentException(location + " was not registered in " + name); return object; } @Nullable @Override public T tryGet(ResourceLocation location) { return registry.get(location); } @Override public T get(int id) { var object = registry.byId(id); if (object == null) throw new IllegalArgumentException(id + " was not registered in " + name); return object; } @Nonnull @Override public Iterator iterator() { return registry.iterator(); } } private static final class RegistrationHelperImpl implements RegistrationHelper { private final Registry registry; private final List> entries = new ArrayList<>(); private RegistrationHelperImpl(Registry registry) { this.registry = registry; } @Override public RegistryEntry register(String name, Supplier create) { var entry = new RegistryEntryImpl<>(new ResourceLocation(ComputerCraftAPI.MOD_ID, name), create); entries.add(entry); return entry; } @Override public void register() { for (var entry : entries) entry.register(registry); } } private static final class RegistryEntryImpl implements RegistryEntry { private final ResourceLocation id; private final Supplier supplier; private @Nullable T instance; RegistryEntryImpl(ResourceLocation id, Supplier supplier) { this.id = id; this.supplier = supplier; } void register(Registry registry) { Registry.register(registry, id, instance = supplier.get()); } @Override public ResourceLocation id() { return id; } @Override public T get() { if (instance == null) throw new IllegalStateException(id + " has not been constructed yet"); return instance; } } private record WrappedMenuProvider(MenuProvider owner, ContainerData menu) implements ExtendedScreenHandlerFactory { @Nullable @Override public AbstractContainerMenu createMenu(int id, Inventory inventory, Player player) { return owner.createMenu(id, inventory, player); } @Override public Component getDisplayName() { return owner.getDisplayName(); } @Override public void writeScreenOpeningData(ServerPlayer player, FriendlyByteBuf buf) { menu.toBytes(buf); } } private static class ComponentAccessImpl implements ComponentAccess { private final BlockApiLookup lookup; @SuppressWarnings({ "unchecked", "rawtypes" }) final BlockApiCache[] caches = new BlockApiCache[6]; private @Nullable Level level; private @Nullable BlockPos pos; private ComponentAccessImpl(BlockApiLookup lookup) { this.lookup = lookup; } @Nullable @Override public T get(ServerLevel level, BlockPos pos, Direction direction) { if (this.level != null && this.level != level) throw new IllegalStateException("Level has changed"); if (this.pos != null && this.pos != pos) throw new IllegalStateException("Position has changed"); this.level = level; this.pos = pos; var cache = caches[direction.ordinal()]; if (cache == null) { cache = caches[direction.ordinal()] = BlockApiCache.create(lookup, level, pos.relative(direction)); } return cache.find(direction.getOpposite()); } } private static final class PeripheralAccessImpl extends ComponentAccessImpl { private PeripheralAccessImpl() { super(PeripheralLookup.get()); } @Nullable @Override public IPeripheral get(ServerLevel level, BlockPos pos, Direction direction) { var result = super.get(level, pos, direction); if (result != null) return result; var cache = caches[direction.ordinal()]; return GenericPeripheralProvider.getPeripheral(level, cache.getPos(), direction.getOpposite(), cache.getBlockEntity()); } } }