WorldSavedData

Minecraft为我们提供了一个名为WorldSavedData的类,用于全局(所有世界使用同一个)或者分维度(原版的三个世界的存储不同)的数据存储,这一部分用于演示的是玩家标记位置数据,所以首先我们需要继承一个WorldSavedData。新建包com.github.ustc_zzzz.fmltutor.worldstorage,并在其中创建一个PositionWorldSavedData类,使其继承WorldSavedData类:

src/main/java/com/github/ustc_zzzz/fmltutor/worldstorage/PositionWorldSavedData.java:

package com.github.ustc_zzzz.fmltutor.worldstorage;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.util.Vec3;
import net.minecraft.world.WorldSavedData;

public class PositionWorldSavedData extends WorldSavedData
{
    private List<Vec3> positions = new ArrayList<Vec3>();
    private List<UUID> players = new ArrayList<UUID>();

    public PositionWorldSavedData(String name)
    {
        super(name);
    }

    public int size()
    {
        return players.size();
    }

    public Vec3 getPosition(int index)
    {
        return positions.get(index);
    }

    public UUID getPlayerUUID(int index)
    {
        return players.get(index);
    }

    public void add(Vec3 position, UUID player)
    {
        positions.add(position);
        players.add(player);
        this.markDirty();
    }

    @Override
    public void readFromNBT(NBTTagCompound nbt)
    {
        positions.clear();
        players.clear();
        NBTTagList list = (NBTTagList) nbt.getTag("positions");
        if (list == null)
        {
            list = new NBTTagList();
        }
        for (int i = list.tagCount() - 1; i >= 0; --i)
        {
            NBTTagCompound compound = (NBTTagCompound) list.get(i);
            positions.add(new Vec3(compound.getDouble("x"), compound.getDouble("y"), compound.getDouble("z")));
            players.add(UUID.fromString(compound.getString("player")));
        }
    }

    @Override
    public void writeToNBT(NBTTagCompound nbt)
    {
        NBTTagList list = new NBTTagList();
        for (int i = players.size() - 1; i >= 0; --i)
        {
            Vec3 position = positions.get(i);
            UUID player = players.get(i);
            NBTTagCompound compound = new NBTTagCompound();
            compound.setDouble("x", position.xCoord);
            compound.setDouble("y", position.yCoord);
            compound.setDouble("z", position.zCoord);
            compound.setString("player", player.toString());
            list.appendTag(compound);
        }
        nbt.setTag("positions", list);
    }
}

首先,WorldSavedData类的子类必须实现一个传入一个字符串(String)的构造方法,也就是这段:

    public PositionWorldSavedData(String name)
    {
        super(name);
    }

然后为了方便使用,我们添加了若干个方法用于获取数据,这里需要注意的有一点:

    public void add(Vec3 position, UUID player)
    {
        positions.add(position);
        players.add(player);
        this.markDirty();
    }

里面调用了的markDirty方法是什么意思呢?

Minecraft会在触发某些状态(比如单人模式时按下Esc)或每隔一段时间保存所有数据。不过为了保证效率,不是所有数据都需要保存,因为大部分的数据在两次保存间隔中没有发生过变动。markDirty方法的作用就是标记这一数据发生了变动,以让Minecraft下一次保存游戏的时候进行保存。

Minecraft中几乎所有数据都使用NBT标签的方式保存,这里就是实现了writeToNBTreadFromNBT方法,用于把WorldSavedData序列化成一个NBT标签,或者把一个NBT标签反序列化成一个WorldSavedData。这两个方法的实现也很简单,这里就不再赘述了。

获取WorldSavedData

这里我们在PositionWorldSavedData类里写两个静态方法,用于获取全局的或者分维度的WorldSavedData

src/main/java/com/github/ustc_zzzz/fmltutor/worldstorage/PositionWorldSavedData.java(部分):

    public static PositionWorldSavedData get(World world)
    {
        WorldSavedData data = world.getPerWorldStorage().loadData(PositionWorldSavedData.class, "FMLTutorPositions");
        if (data == null)
        {
            data = new PositionWorldSavedData("FMLTutorPositions");
            world.getPerWorldStorage().setData("FMLTutorPositions", data);
        }
        return (PositionWorldSavedData) data;
    }

    public static PositionWorldSavedData getGlobal(World world)
    {
        WorldSavedData data = world.getMapStorage().loadData(PositionWorldSavedData.class, "FMLTutorPositionsGlobal");
        if (data == null)
        {
            data = new PositionWorldSavedData("FMLTutorPositionsGlobal");
            world.getMapStorage().setData("FMLTutorPositionsGlobal", data);
        }
        return (PositionWorldSavedData) data;
    }

首先,World类提供了名为getPerWorldStorage方法用于获取分维度数据,同时提供了名为getMapStorage的方法用于获取一个全局数据。它们都返回一个MapStorageMapStorage有两个方法需要被注意:loadDatasetData,它们分别被用于获取和设置WorldSavedData

loadData方法的第一个参数传入PositionWorldSavedData的子类对应的class,第二个参数传入一个标识符。这里如果返回null,就代表对应的数据并未存在过,我们就需要对其进行设置。

setData就是用于设置的。它的第一个参数传入一个标识符,第二个参数传入想要设置的WorldSavedData

这里有一点需要注意的是,用于全局的数据存储和分维度的数据存储,它们的标识符决不能相同。因为主世界维度的数据存储和全局的数据存储位置是重合的。读者可以在存档的data目录下找到标识符对应的,添加了名为.dat后缀名的存储文件,教程里就是data/FMLTutorPositionsGlobal.dat,这一文件表示全局数据存储和主世界维度的数据存储。下界、末界等的数据存储可以在DIM-1/dataDIM1/data等目录下找到,教程里对应三个维度的就是data/FMLTutorPositions.datDIM-1/data/FMLTutorPositions.dat、和DIM1/data/FMLTutorPositions.dat

进行了简单的包装,我们就可以直接使用WorldSavedData了。

一些和教程核心内容关联不大的实现

仅仅是作为演示,我们添加了用于系统命令,也就是之前曾经提到过的/position的世界附加数据存储:

src/main/java/com/github/ustc_zzzz/fmltutor/command/CommandPosition.java(部分):

    @Override
    public void processCommand(ICommandSender sender, String[] args) throws CommandException
    {
        if (args.length == 2 && "mark".equals(args[0]))
        {
            EntityPlayerMP entityPlayerMP = CommandBase.getCommandSenderAsPlayer(sender);
            if ("list".equals(args[1]))
            {
                PositionWorldSavedData data;
                sender.addChatMessage(new ChatComponentText("Global: "));
                data = PositionWorldSavedData.getGlobal(entityPlayerMP.worldObj);
                for (int i = data.size() - 1; i >= 0; --i)
                {
                    sender.addChatMessage(new ChatComponentTranslation("commands.position.mark", data.getPosition(i),
                            entityPlayerMP.worldObj.getPlayerEntityByUUID(data.getPlayerUUID(i)).getName()));
                }
                sender.addChatMessage(new ChatComponentText("Dimension: "));
                data = PositionWorldSavedData.get(entityPlayerMP.worldObj);
                for (int i = data.size() - 1; i >= 0; --i)
                {
                    sender.addChatMessage(new ChatComponentTranslation("commands.position.mark", data.getPosition(i),
                            entityPlayerMP.worldObj.getPlayerEntityByUUID(data.getPlayerUUID(i)).getName()));
                }
            }
            else if ("global".equals(args[1]))
            {
                Vec3 pos = entityPlayerMP.getPositionVector();
                UUID uuid = entityPlayerMP.getUniqueID();
                PositionWorldSavedData.getGlobal(entityPlayerMP.worldObj).add(pos, uuid);
                sender.addChatMessage(new ChatComponentTranslation("commands.position.marked", pos));
            }
            else if ("dimension".equals(args[1]))
            {
                Vec3 pos = entityPlayerMP.getPositionVector();
                UUID uuid = entityPlayerMP.getUniqueID();
                PositionWorldSavedData.get(entityPlayerMP.worldObj).add(pos, uuid);
                sender.addChatMessage(new ChatComponentTranslation("commands.position.marked", pos));
            }
            return;
        }
        if (args.length > 1)
        {
            throw new WrongUsageException("commands.position.usage");
        }
        else
        {
            EntityPlayerMP entityPlayerMP = args.length > 0 ? CommandBase.getPlayer(sender, args[0])
                    : CommandBase.getCommandSenderAsPlayer(sender);
            Vec3 pos = entityPlayerMP.getPositionVector();

            if (entityPlayerMP == sender && entityPlayerMP.hasCapability(CapabilityLoader.positionHistory, null))
            {
                sender.addChatMessage(new ChatComponentTranslation("commands.position.history"));
                IPositionHistory histories = entityPlayerMP.getCapability(CapabilityLoader.positionHistory, null);
                for (Vec3 vec3 : histories.getHistories())
                {
                    if (vec3 != null)
                    {
                        sender.addChatMessage(new ChatComponentText(vec3.toString()));
                    }
                }
                histories.pushHistory(pos);

                MessagePositionHistory message = new MessagePositionHistory();
                IStorage<IPositionHistory> storage = CapabilityLoader.positionHistory.getStorage();

                message.nbt = new NBTTagCompound();
                message.nbt.setTag("histories", storage.writeNBT(CapabilityLoader.positionHistory, histories, null));

                NetworkLoader.instance.sendTo(message, entityPlayerMP);
            }

            sender.addChatMessage(new ChatComponentTranslation("commands.position.success", entityPlayerMP.getName(),
                    pos, entityPlayerMP.worldObj.provider.getDimensionName()));
        }
    }

    @Override
    public List<String> addTabCompletionOptions(ICommandSender sender, String[] args, BlockPos pos)
    {
        if (args.length == 1)
        {
            String[] names = MinecraftServer.getServer().getAllUsernames();
            names = Arrays.copyOf(names, names.length + 1);
            names[names.length - 1] = "mark";
            return CommandBase.getListOfStringsMatchingLastWord(args, names);
        }
        else if (args.length == 2 && "mark".equals(args[0]))
        {
            return CommandBase.getListOfStringsMatchingLastWord(args, "list", "global", "dimension");
        }
        return null;
    }

还有两个语言文件:

src/main/resources/assets/fmltutor/lang/en_US.lang(部分):

commands.position.mark=%s, by %s
commands.position.marked=Marked %s

src/main/resources/assets/fmltutor/lang/zh_CN.lang(部分):

commands.position.mark=%s,来自于 %s
commands.position.marked=已标记 %s

results matching ""

    No results matching ""