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标签的方式保存,这里就是实现了writeToNBT
和readFromNBT
方法,用于把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
的方法用于获取一个全局数据。它们都返回一个MapStorage
。MapStorage
有两个方法需要被注意:loadData
和setData
,它们分别被用于获取和设置WorldSavedData
。
loadData
方法的第一个参数传入PositionWorldSavedData
的子类对应的class,第二个参数传入一个标识符。这里如果返回null
,就代表对应的数据并未存在过,我们就需要对其进行设置。
setData
就是用于设置的。它的第一个参数传入一个标识符,第二个参数传入想要设置的WorldSavedData
。
这里有一点需要注意的是,用于全局的数据存储和分维度的数据存储,它们的标识符决不能相同。因为主世界维度的数据存储和全局的数据存储位置是重合的。读者可以在存档的data
目录下找到标识符对应的,添加了名为.dat
后缀名的存储文件,教程里就是data/FMLTutorPositionsGlobal.dat
,这一文件表示全局数据存储和主世界维度的数据存储。下界、末界等的数据存储可以在DIM-1/data
和DIM1/data
等目录下找到,教程里对应三个维度的就是data/FMLTutorPositions.dat
、DIM-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