逆向移植Bukkit的BlockDataAPI(1)

前言

前言

Bukkit在11300扩展了NMS中的BlockData相关逻辑,提供了一系列用于判断和解析方块状态的接口,此接口对应org.bukkit.block.data.*,实现位于org.bukkit.craftbukkit.block.data.*,那么存不存在将这些便捷的方法向后移植,比如移植到11202的可能呢?

可行性分析

  • 首先,Bukkit扩展的大多数方法无外乎为方块状态的判断/获取/设置,虽然在低版本中Bukkit并没有提供这些接口,但它们早已存在于NMS当中,因此,只要我们能够正确的比对和分析NMS的BlockData方法调用链,在API变化相对不是很大的版本之间(11300 -> 11202)将完全相同的逻辑向下移植是不存在问题的
  • 其次,关于BlockData的解析可能会遇到一些困难,因为Mojang在11300引入了全新的命令解析系统Brigadier,它虽然名义上为“命令解析系统”,但实际上被广泛用于NMS内部的反序列化中,BlockData也不例外。由于Brigadier在11300以前的版本中根本不存在,高版本存在的相关的逻辑更是无从谈起(比如ArgumentBlock),为了成功移植这一部分,我们将不得不考虑手动回写高版本的所有相关逻辑

注:

1.术语Bukkit:MC第三方服务端软件,为原版MC服务端扩展了极多的外部开发接口,由Bukkit团队开发,因直接分发反混淆后的MC服务端原代码被Mojang 以侵权 DMCA Takedown,目前社区由SpigotMC接手其开发,采用BuildTools分发反混淆后的MC服务端源代码以及Bukkit的Fork: Spigot

2.术语CraftBukkit:Bukkit接口的实现,由Bukkit团队开发,目前由SpigotMC接手

3.术语BuildData:Bukkit采用和维护的MC反混淆方案,与MCP(Forge采用)、Yarn(Fabric采用)相同,旨在以人为臆测的方式反混淆MC这个商业混淆软件中的代码,目前由SpigotMC接手

4.术语NMS:net.minecraft.server包名的缩写,是BuildData在反混淆MC服务端源码时假想的MC服务端代码结构,包含了Mojang为MC服务端编写的所有底层代码(1.16.5以前),CraftBukkit通过对它的修改来得到接口方法,最终提供给Bukkit

5.关于版本:上方和下方对于MC版本的声明使用不加”.“的形式,如11202代表1.12.2,对NMS的声明使用以”_“分隔的后两部分,如12R1代表1.12.2的NMS

准备工作

  • 11202的MC服务端源码:BuildTools获取org.spigotmc.minecraftserver 后在本地部署任意Spigot Fork源码即可得到,推荐使用Paper,部署方式为使用git ./paper p(1.16.5及以前)
  • 11400的MC服务端源码:如上
  • 11605的MC服务端源码:如上,因为PDC的部分功能在11400更后期的版本加入,故需要更高版本的服务端源码

接口改造

此部分不多介绍,我们需要从高版本回写org.bukkit.block.data.type.*包下的接口
并在org.bukkit.block.data.Block中添加

java 复制代码
    /**
     * Gets the complete block data for this block
     *
     * @return block specific data
     */
    @NotNull
    BlockData getBlockData();

在org.bukkit.block.data.BlockState中添加

java 复制代码
    /**
     * Gets the data for this block state.
     *
     * @return block specific data
     */
    @NotNull
    BlockData getBlockData();

接口实现

首先我们需要回写org.bukkit.craftbukkit.block.data.CraftBlockData
注意部分来自高版本的方块没有对应的实现

java 复制代码
package org.bukkit.craftbukkit.block.data

import java.util.stream.Collectors;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import net.minecraft.server.*;
import org.bukkit.Material;
import org.bukkit.block.BlockFace;
import org.bukkit.block.data.BlockData;
import org.bukkit.craftbukkit.block.CraftBlock;
import org.bukkit.craftbukkit.util.CraftMagicNumbers;
import org.jetbrains.annotations.NotNull;

public class CraftBlockData implements BlockData {

    private IBlockData state;
    private Map<IBlockState<?>, Comparable<?>> parsedStates;

    protected CraftBlockData() {
        throw new AssertionError("Template Constructor");
    }

    protected CraftBlockData(IBlockData state) {
        this.state = state;
    }

    @Override
    public @NotNull Material getMaterial() {
        return CraftMagicNumbers.getMaterial(state.getBlock());
    }

    public IBlockData getState() {
        return state;
    }

    /**
     * Get a given BlockStateEnum's value as its Bukkit counterpart.
     *
     * @param nms the NMS state to convert
     * @param bukkit the Bukkit class
     * @param <B> the type
     * @return the matching Bukkit type
     */
    protected <B extends Enum<B>> B get(BlockStateEnum<?> nms, Class<B> bukkit) {
        return toBukkit(state.get(nms), bukkit);
    }

    /**
     * Convert all values from the given BlockStateEnum to their appropriate
     * Bukkit counterpart.
     *
     * @param nms the NMS state to get values from
     * @param bukkit the bukkit class to convert the values to
     * @param <B> the bukkit class type
     * @return an immutable Set of values in their appropriate Bukkit type
     */
    protected <B extends Enum<B>> Set<B> getValues(BlockStateEnum<?> nms, Class<B> bukkit) {
        ImmutableSet.Builder<B> values = ImmutableSet.builder();

        for (Enum<?> e : nms.c()) {
            values.add(toBukkit(e, bukkit));
        }

        return values.build();
    }

    /**
     * Set a given {@link BlockStateEnum} with the matching enum from Bukkit.
     *
     * @param nms the NMS BlockStateEnum to set
     * @param bukkit the matching Bukkit Enum
     * @param <B> the Bukkit type
     * @param <N> the NMS type
     */
    protected <B extends Enum<B>, N extends Enum<N> & INamable> void set(BlockStateEnum<N> nms, Enum<B> bukkit) {
        this.parsedStates = null;
        this.state = this.state.set(nms, toNMS(bukkit, nms.b()));
    }

    @Override
    public @NotNull BlockData merge(@NotNull BlockData data) {
        CraftBlockData craft = (CraftBlockData) data;
        Preconditions.checkArgument(craft.parsedStates != null, "Data not created via string parsing");
        Preconditions.checkArgument(this.state.getBlock() == craft.state.getBlock(), "States have different types (got %s, expected %s)", data, this);

        CraftBlockData clone = (CraftBlockData) this.clone();
        clone.parsedStates = null;

        for (IBlockState parsed : craft.parsedStates.keySet()) {
            clone.state = clone.state.set(parsed, craft.state.get(parsed));
        }

        return clone;
    }

    @Override
    public boolean matches(BlockData data) {
        if (data == null) {
            return false;
        }
        if (!(data instanceof CraftBlockData)) {
            return false;
        }

        CraftBlockData craft = (CraftBlockData) data;
        if (this.state.getBlock() != craft.state.getBlock()) {
            return false;
        }

        // Fastpath an exact match
        boolean exactMatch = this.equals(data);

        // If that failed, do a merge and check
        if (!exactMatch && craft.parsedStates != null) {
            return this.merge(data).equals(this);
        }

        return exactMatch;
    }

    private static final Map<Class, BiMap<Enum<?>, Enum<?>>> classMappings = new HashMap<>();

    /**
     * Convert an NMS Enum (usually a BlockStateEnum) to its appropriate Bukkit
     * enum from the given class.
     *
     * @throws IllegalStateException if the Enum could not be converted
     */
    @SuppressWarnings("unchecked")
    private static <B extends Enum<B>> B toBukkit(Enum<?> nms, Class<B> bukkit) {
        Enum<?> converted;
        BiMap<Enum<?>, Enum<?>> nmsToBukkit = classMappings.get(nms.getClass());

        if (nmsToBukkit != null) {
            converted = nmsToBukkit.get(nms);
            if (converted != null) {
                return (B) converted;
            }
        }

        if (nms instanceof EnumDirection) {
            converted = CraftBlock.notchToBlockFace((EnumDirection) nms);
        } else {
            converted = bukkit.getEnumConstants()[nms.ordinal()];
        }

        Preconditions.checkState(converted != null, "Could not convert enum %s->%s", nms, bukkit);

        if (nmsToBukkit == null) {
            nmsToBukkit = HashBiMap.create();
            classMappings.put(nms.getClass(), nmsToBukkit);
        }

        nmsToBukkit.put(nms, converted);

        return (B) converted;
    }

    /**
     * Convert a given Bukkit enum to its matching NMS enum type.
     *
     * @param bukkit the Bukkit enum to convert
     * @param nms the NMS class
     * @return the matching NMS type
     * @throws IllegalStateException if the Enum could not be converted
     */
    @SuppressWarnings("unchecked")
    private static <N extends Enum<N> & INamable> N toNMS(Enum<?> bukkit, Class<N> nms) {
        Enum<?> converted;
        BiMap<Enum<?>, Enum<?>> nmsToBukkit = classMappings.get(nms);

        if (nmsToBukkit != null) {
            converted = nmsToBukkit.inverse().get(bukkit);
            if (converted != null) {
                return (N) converted;
            }
        }

        if (bukkit instanceof BlockFace) {
            converted = CraftBlock.blockFaceToNotch((BlockFace) bukkit);
        } else {
            converted = nms.getEnumConstants()[bukkit.ordinal()];
        }

        Preconditions.checkState(converted != null, "Could not convert enum %s->%s", nms, bukkit);

        if (nmsToBukkit == null) {
            nmsToBukkit = HashBiMap.create();
            classMappings.put(nms, nmsToBukkit);
        }

        nmsToBukkit.put(converted, bukkit);

        return (N) converted;
    }

    /**
     * Get the current value of a given state.
     *
     * @param ibs the state to check
     * @param <T> the type
     * @return the current value of the given state
     */
    protected <T extends Comparable<T>> T get(IBlockState<T> ibs) {
        // Straight integer or boolean getter
        return this.state.get(ibs);
    }

    /**
     * Set the specified state's value.
     *
     * @param ibs the state to set
     * @param v the new value
     * @param <T> the state's type
     * @param <V> the value's type. Must match the state's type.
     */
    public <T extends Comparable<T>, V extends T> void set(IBlockState<T> ibs, V v) {
        // Straight integer or boolean setter
        this.parsedStates = null;
        this.state = this.state.set(ibs, v);
    }

    @Override
    public @NotNull String getAsString() {
        return toString(((BlockStateList.BlockData) state).t());
    }

    @Override
    public @NotNull String getAsString(boolean hideUnspecified) {
        return (hideUnspecified && parsedStates != null) ? toString(parsedStates) : getAsString();
    }

    @Override
    public @NotNull BlockData clone() {
        try {
            return (BlockData) super.clone();
        } catch (CloneNotSupportedException ex) {
            throw new AssertionError("Clone not supported", ex);
        }
    }

    @Override
    public String toString() {
        return "CraftBlockData{" + state.toString() + "}";
    }

    // Mimicked from BlockDataAbstract#toString()
    public String toString(Map<IBlockState<?>, Comparable<?>> states) {
        StringBuilder stateString = new StringBuilder(Block.REGISTRY.b(state.getBlock()).toString());

        if (!states.isEmpty()) {
            stateString.append('[');
            stateString.append(states.entrySet().stream().map(BlockDataAbstract.b).collect(Collectors.joining(",")));
            stateString.append(']');
        }

        return stateString.toString();
    }

    @Override
    public boolean equals(Object obj) {
        return obj instanceof CraftBlockData && state.equals(((CraftBlockData) obj).state);
    }

    @Override
    public int hashCode() {
        return state.hashCode();
    }

    protected static BlockStateBoolean getBoolean(String name) {
        throw new AssertionError("Template Method");
    }

    protected static BlockStateBoolean getBoolean(String name, boolean optional) {
        throw new AssertionError("Template Method");
    }

    protected static BlockStateEnum<?> getEnum(String name) {
        throw new AssertionError("Template Method");
    }

    protected static BlockStateInteger getInteger(String name) {
        throw new AssertionError("Template Method");
    }

    protected static BlockStateBoolean getBoolean(Class<? extends Block> block, String name) {
        return (BlockStateBoolean) getState(block, name, false);
    }

    protected static BlockStateBoolean getBoolean(Class<? extends Block> block, String name, boolean optional) {
        return (BlockStateBoolean) getState(block, name, optional);
    }

    protected static BlockStateEnum<?> getEnum(Class<? extends Block> block, String name) {
        return (BlockStateEnum<?>) getState(block, name, false);
    }

    protected static BlockStateInteger getInteger(Class<? extends Block> block, String name) {
        return (BlockStateInteger) getState(block, name, false);
    }

    /**
     * Get a specified {@link IBlockState} from a given block's class with a
     * given name
     *
     * @param block the class to retrieve the state from
     * @param name the name of the state to retrieve
     * @param optional if the state can be null
     * @return the specified state or null
     * @throws IllegalStateException if the state is null and {@code optional}
     * is false.
     */
    private static IBlockState<?> getState(Class<? extends Block> block, String name, boolean optional) {
        IBlockState<?> state = null;

        for (Block instance : (Iterable<Block>) Block.REGISTRY) { // Eclipse fail
            if (instance.getClass() == block) {
                if (state == null) {
                    state = instance.getStateList().a(name);
                } else {
                    IBlockState<?> newState = instance.getStateList().a(name);

                    Preconditions.checkState(state == newState, "State mistmatch %s,%s", state, newState);
                }
            }
        }

        Preconditions.checkState(optional || state != null, "Null state for %s,%s", block, name);

        return state;
    }

    /**
     * Get the minimum value allowed by the BlockStateInteger.
     *
     * @param state the state to check
     * @return the minimum value allowed
     */
    protected static int getMin(BlockStateInteger state) {
        return state.min;
    }

    /**
     * Get the maximum value allowed by the BlockStateInteger.
     *
     * @param state the state to check
     * @return the maximum value allowed
     */
    protected static int getMax(BlockStateInteger state) {
        return state.max;
    }

    //
    private static final Map<Class<? extends Block>, Function<IBlockData, CraftBlockData>> MAP = new HashMap<>();

    static {
        //<editor-fold desc="CraftBlockData Registration" defaultstate="collapsed">
        register(net.minecraft.server.BlockAnvil.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftAnvil::new);
        register(net.minecraft.server.BlockBanner.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftBanner::new);
        //11200旗帜与墙上旗帜共享一个类net.minecraft.server.BlockBanner
        //register(net.minecraft.server.BlockBannerWall.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftBannerWall::new);
        register(net.minecraft.server.BlockBed.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftBed::new);
        register(net.minecraft.server.BlockBeetroot.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftBeetroot::new);
        register(net.minecraft.server.BlockBrewingStand.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftBrewingStand::new);
        //11300加入
        //register(net.minecraft.server.BlockBubbleColumn.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftBubbleColumn::new);
        register(net.minecraft.server.BlockCactus.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftCactus::new);
        register(net.minecraft.server.BlockCake.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftCake::new);
        register(net.minecraft.server.BlockCarrots.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftCarrots::new);
        register(net.minecraft.server.BlockCauldron.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftCauldron::new);
        register(net.minecraft.server.BlockChest.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftChest::new);
        //11200箱子与陷阱箱共享一个类net.minecraft.server.BlockChest
        //register(net.minecraft.server.BlockChestTrapped.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftChestTrapped::new);
        register(net.minecraft.server.BlockChorusFlower.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftChorusFlower::new);
        register(net.minecraft.server.BlockChorusFruit.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftChorusFruit::new);
        register(net.minecraft.server.BlockCobbleWall.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftCobbleWall::new);
        register(net.minecraft.server.BlockCocoa.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftCocoa::new);
        register(net.minecraft.server.BlockCommand.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftCommand::new);
        //11300加入
        //register(net.minecraft.server.BlockConduit.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftConduit::new);
        //register(net.minecraft.server.BlockCoralDead.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftCoralDead::new);
        //register(net.minecraft.server.BlockCoralFan.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftCoralFan::new);
        //register(net.minecraft.server.BlockCoralFanAbstract.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftCoralFanAbstract::new);
        //register(net.minecraft.server.BlockCoralFanWall.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftCoralFanWall::new);
        //register(net.minecraft.server.BlockCoralFanWallAbstract.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftCoralFanWallAbstract::new);
        //register(net.minecraft.server.BlockCoralPlant.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftCoralPlant::new);
        register(net.minecraft.server.BlockCrops.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftCrops::new);
        register(net.minecraft.server.BlockDaylightDetector.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftDaylightDetector::new);
        //11200通过metadata实现
        //register(net.minecraft.server.BlockDirtSnow.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftDirtSnow::new);
        register(net.minecraft.server.BlockDispenser.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftDispenser::new);
        register(net.minecraft.server.BlockDoor.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftDoor::new);
        register(net.minecraft.server.BlockDropper.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftDropper::new);
        register(net.minecraft.server.BlockEndRod.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftEndRod::new);
        register(net.minecraft.server.BlockEnderChest.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftEnderChest::new);
        register(net.minecraft.server.BlockEnderPortalFrame.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftEnderPortalFrame::new);
        register(net.minecraft.server.BlockFence.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftFence::new);
        register(net.minecraft.server.BlockFenceGate.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftFenceGate::new);
        register(net.minecraft.server.BlockFire.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftFire::new);
        register(net.minecraft.server.BlockFloorSign.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftFloorSign::new);
        register(net.minecraft.server.BlockFluids.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftFluids::new);
        register(net.minecraft.server.BlockFurnace.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftFurnace::new);
        //11300加入
        //register(net.minecraft.server.BlockGlassPane.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftGlassPane::new);
        register(net.minecraft.server.BlockGlazedTerracotta.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftGlazedTerracotta::new);
        register(net.minecraft.server.BlockGrass.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftGrass::new);
        register(net.minecraft.server.BlockHay.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftHay::new);
        register(net.minecraft.server.BlockHopper.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftHopper::new);
        register(net.minecraft.server.BlockHugeMushroom.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftHugeMushroom::new);
        register(net.minecraft.server.BlockIceFrost.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftIceFrost::new);
        //11300加入
        //register(net.minecraft.server.BlockIronBars.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftIronBars::new);
        register(net.minecraft.server.BlockJukeBox.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftJukeBox::new);
        //11300加入
        //register(net.minecraft.server.BlockKelp.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftKelp::new);
        register(net.minecraft.server.BlockLadder.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftLadder::new);
        register(net.minecraft.server.BlockLeaves.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftLeaves::new);
        register(net.minecraft.server.BlockLever.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftLever::new);
        register(net.minecraft.server.BlockLogAbstract.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftLogAbstract::new);
        register(net.minecraft.server.BlockMinecartDetector.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftMinecartDetector::new);
        register(net.minecraft.server.BlockMinecartTrack.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftMinecartTrack::new);
        register(net.minecraft.server.BlockMycel.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftMycel::new);
        register(net.minecraft.server.BlockNetherWart.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftNetherWart::new);
        register(net.minecraft.server.BlockNote.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftNote::new);
        register(net.minecraft.server.BlockObserver.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftObserver::new);
        register(net.minecraft.server.BlockPiston.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftPiston::new);
        register(net.minecraft.server.BlockPistonExtension.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftPistonExtension::new);
        register(net.minecraft.server.BlockPistonMoving.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftPistonMoving::new);
        register(net.minecraft.server.BlockPortal.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftPortal::new);
        register(net.minecraft.server.BlockPotatoes.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftPotatoes::new);
        register(net.minecraft.server.BlockPoweredRail.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftPoweredRail::new);
        register(net.minecraft.server.BlockPressurePlateBinary.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftPressurePlateBinary::new);
        register(net.minecraft.server.BlockPressurePlateWeighted.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftPressurePlateWeighted::new);
        //11300加入
        //register(net.minecraft.server.BlockPumpkinCarved.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftPumpkinCarved::new);
        register(net.minecraft.server.BlockRedstoneComparator.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftRedstoneComparator::new);
        register(net.minecraft.server.BlockRedstoneLamp.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftRedstoneLamp::new);
        register(net.minecraft.server.BlockRedstoneOre.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftRedstoneOre::new);
        register(net.minecraft.server.BlockRedstoneTorch.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftRedstoneTorch::new);
        //11200通过metadata区分
        //register(net.minecraft.server.BlockRedstoneTorchWall.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftRedstoneTorchWall::new);
        register(net.minecraft.server.BlockRedstoneWire.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftRedstoneWire::new);
        register(net.minecraft.server.BlockReed.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftReed::new);
        register(net.minecraft.server.BlockRepeater.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftRepeater::new);
        register(net.minecraft.server.BlockRotatable.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftRotatable::new);
        register(net.minecraft.server.BlockSapling.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftSapling::new);
        //11300加入
        //register(net.minecraft.server.BlockSeaPickle.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftSeaPickle::new);
        register(net.minecraft.server.BlockShulkerBox.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftShulkerBox::new);
        register(net.minecraft.server.BlockSkull.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftSkull::new);
        //11300加入
        //register(net.minecraft.server.BlockSkullPlayer.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftSkullPlayer::new);
        //register(net.minecraft.server.BlockSkullPlayerWall.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftSkullPlayerWall::new);
        //register(net.minecraft.server.BlockSkullWall.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftSkullWall::new);
        register(net.minecraft.server.BlockSnow.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftSnow::new);
        register(net.minecraft.server.BlockSoil.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftSoil::new);
        register(net.minecraft.server.BlockStainedGlassPane.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftStainedGlassPane::new);
        register(net.minecraft.server.BlockStairs.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftStairs::new);
        register(net.minecraft.server.BlockStem.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftStem::new);
        //11200共享一个类net.minecraft.server.BlockStem
        //register(net.minecraft.server.BlockStemAttached.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftStemAttached::new);
        register(net.minecraft.server.BlockStepAbstract.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftStepAbstract::new);
        register(net.minecraft.server.BlockStoneButton.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftStoneButton::new);
        register(net.minecraft.server.BlockStructure.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftStructure::new);
        register(net.minecraft.server.BlockTNT.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftTNT::new);
        //11200通过metadata实现
        //register(net.minecraft.server.BlockTallPlantFlower.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftTallPlantFlower::new);
        //register(net.minecraft.server.BlockTallPlantShearable.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftTallPlantShearable::new);
        //register(net.minecraft.server.BlockTallSeaGrass.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftTallSeaGrass::new);
        //register(net.minecraft.server.BlockTorchWall.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftTorchWall::new);
        register(net.minecraft.server.BlockTrapdoor.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftTrapdoor::new);
        register(net.minecraft.server.BlockTripwire.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftTripwire::new);
        register(net.minecraft.server.BlockTripwireHook.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftTripwireHook::new);
        //11300加入
        //register(net.minecraft.server.BlockTurtleEgg.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftTurtleEgg::new);
        register(net.minecraft.server.BlockVine.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftVine::new);
        register(net.minecraft.server.BlockWallSign.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftWallSign::new);
        //11200共享一个类net.minecraft.block.BlockSkull
        //register(net.minecraft.server.BlockWitherSkull.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftWitherSkull::new);
        //register(net.minecraft.server.BlockWitherSkullWall.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftWitherSkullWall::new);
        register(net.minecraft.server.BlockWoodButton.class, org.bukkit.craftbukkit.block.data.blockdata.impl.CraftWoodButton::new);
        //</editor-fold>
    }

    private static void register(Class<? extends Block> nms, Function<IBlockData, CraftBlockData> bukkit) {
        Preconditions.checkState(MAP.put(nms, bukkit) == null, "Duplicate mapping %s->%s", nms, bukkit);
    }

    // Paper start - optimize creating BlockData to not need a map lookup
    static {
        // Initialize cached data for all IBlockData instances after registration
        Block.REGISTRY_ID.iterator().forEachRemaining(IBlockData::createCraftBlockData);
    }
    public static CraftBlockData fromData(IBlockData data) {
        return data.createCraftBlockData();
    }

    public static CraftBlockData createData(IBlockData data) {
        // Paper end
        return MAP.getOrDefault(data.getBlock().getClass(), CraftBlockData::new).apply(data);
    }
}

并回写org.bukkit.craft.block.data.impl下的内容,这里不多叙述,注意这些实现中的反射字段在迁移至低版本时可能需要进行一定的修改,部分的方块可能缺少对应的反射字段

之后,完善接口实现
在org.bukkit.craftbukkit.block.CraftBlock中添加

java 复制代码
    private final net.minecraft.server.World world;
    private final net.minecraft.server.BlockPosition position;

    public net.minecraft.server.IBlockData getNMS() {
        return world.getType(position);
    }

    @Override
    public org.bukkit.block.data.BlockData getBlockData() {
        return org.bukkit.craftbukkit.block.data.CraftBlockData.fromData(getData0());
    }

完善其构造函数,使其初始化上面我们新增的两个字段

java 复制代码
    public CraftBlock(CraftChunk chunk, int x, int y, int z) {
	...
        this.world = chunk.getCraftWorld().getHandle();
        this.position = new BlockPosition(x, y, z);
	...
    }

在org.bukkit.craftbukkit.block.BlockState中添加

java 复制代码
    protected IBlockData blockData;

    @Override
    public org.bukkit.block.data.BlockData getBlockData() {
        return org.bukkit.craftbukkit.block.data.CraftBlockData.fromData(blockData);
    }

完善其构造函数,使其初始化上面我们新增的字段

java 复制代码
    public CraftBlockState(final Block block) {
	...
        this.blockData = ((CraftBlock) block).getNMS();
        ...
    }

NMS调用链回迁

首先在NMS net.minecraft.server.IBlockData回写Paper获取BlockData的方法

java 复制代码
org.bukkit.craftbukkit.block.data.CraftBlockData createCraftBlockData(); // Paper - property for converting IBlockData to CraftBlockData, should only be used by CraftBlockData#fromData and should return a clone

回写高版本NMS BlockData抽象类net.minecraft.server.BlockDataAbstract

java 复制代码
package net.minecraft.server;

import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.Iterables;
import net.minecraft.server.Block;
import net.minecraft.server.IBlockData;
import net.minecraft.server.IBlockState;
import org.jetbrains.annotations.NotNull;

import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import javax.annotation.Nullable;

public abstract class BlockDataAbstract implements IBlockData {
    private static final Joiner a = Joiner.on(',');
    // Make public
    public static final Function<Map.Entry<IBlockState<?>, Comparable<?>>, String> b = new Function<>() {
        public @NotNull String apply(@Nullable Map.Entry<IBlockState<?>, Comparable<?>> var1) {
            if (var1 == null) {
                return "<NULL>";
            } else {
                IBlockState var2 = (IBlockState) var1.getKey();
                return var2.a() + "=" + this.a(var2, (Comparable) var1.getValue());
            }
        }

        private <T extends Comparable<T>> String a(IBlockState<T> var1, Comparable<?> var2) {
            return var1.a((T) var2);
        }
    };

    public BlockDataAbstract() {
    }

    public <T extends Comparable<T>> IBlockData a(IBlockState<T> var1) {
        return this.set(var1, (Comparable)a(var1.c(), this.get(var1)));
    }

    protected static <T> T a(Collection<T> var0, T var1) {
        Iterator var2 = var0.iterator();

        while(var2.hasNext()) {
            if (var2.next().equals(var1)) {
                if (var2.hasNext()) {
                    return (T)var2.next();
                }

                return (T)var0.iterator().next();
            }
        }

        return (T)var2.next();
    }

    public String toString() {
        StringBuilder var1 = new StringBuilder();
        var1.append(Block.REGISTRY.b(this.getBlock()));
        if (!this.t().isEmpty()) {
            var1.append("[");
            a.appendTo(var1, Iterables.transform(this.t().entrySet(), b));
            var1.append("]");
        }

        return var1.toString();
    }
}

从NMS net.minecraft.server.BlockStateList中获取BlockData,使其为public,并添加Paper的方法

java 复制代码
    public static class BlockData extends BlockDataAbstract {
    ...
        // Paper start - impl cached craft block data, lazy load to fix issue with loading at the wrong time
        private org.bukkit.craftbukkit.block.data.CraftBlockData cachedCraftBlockData;

        @Override
        public org.bukkit.craftbukkit.block.data.CraftBlockData createCraftBlockData() {
            if(cachedCraftBlockData == null) cachedCraftBlockData = org.bukkit.craftbukkit.block.data.CraftBlockData.createData(this);
            return (org.bukkit.craftbukkit.block.data.CraftBlockData) cachedCraftBlockData.clone();
        }
        // Paper end
    ...
    }

最后,使NMS net.minecraft.server.Block中的getStateList()方法为public,如此修改后的CraftBlockData获取原生NMS BlockData的调用链基本回迁完毕

java 复制代码
    public BlockStateList getStateList() {
        return new BlockStateList(this, new IBlockState[0]);
    }

最后

我们基本回迁了80%的BlockData获取,但并未完全完成

此外BlockData的序列化/反序列化在本节并未涉及,正如可行性分析中提到的第二点,对于此部分的迁移将异常简单,但并非不可能(笔者此部分的回迁已经稳定通过了测试,虽然不保证在生产环境下可用)此部分的回迁将在以后更新

游客

全部评论 (0)

暂无评论,快来抢沙发吧~