Plex-FAWE/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/Spigot_v1_13_R1.java
2018-08-13 03:31:26 +10:00

481 lines
19 KiB
Java

/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.boydti.fawe.bukkit.adapter;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import com.sk89q.jnbt.ByteArrayTag;
import com.sk89q.jnbt.ByteTag;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.DoubleTag;
import com.sk89q.jnbt.EndTag;
import com.sk89q.jnbt.FloatTag;
import com.sk89q.jnbt.IntArrayTag;
import com.sk89q.jnbt.IntTag;
import com.sk89q.jnbt.ListTag;
import com.sk89q.jnbt.LongTag;
import com.sk89q.jnbt.NBTConstants;
import com.sk89q.jnbt.ShortTag;
import com.sk89q.jnbt.StringTag;
import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.internal.Constants;
import com.sk89q.worldedit.registry.state.BooleanProperty;
import com.sk89q.worldedit.registry.state.DirectionalProperty;
import com.sk89q.worldedit.registry.state.EnumProperty;
import com.sk89q.worldedit.registry.state.IntegerProperty;
import com.sk89q.worldedit.registry.state.Property;
import com.sk89q.worldedit.util.Direction;
import com.sk89q.worldedit.world.block.BlockStateHolder;
import com.sk89q.worldedit.world.block.BlockType;
import net.minecraft.server.v1_13_R1.BiomeBase;
import net.minecraft.server.v1_13_R1.Block;
import net.minecraft.server.v1_13_R1.BlockPosition;
import net.minecraft.server.v1_13_R1.BlockStateBoolean;
import net.minecraft.server.v1_13_R1.BlockStateDirection;
import net.minecraft.server.v1_13_R1.BlockStateEnum;
import net.minecraft.server.v1_13_R1.BlockStateInteger;
import net.minecraft.server.v1_13_R1.BlockStateList;
import net.minecraft.server.v1_13_R1.Entity;
import net.minecraft.server.v1_13_R1.EntityTypes;
import net.minecraft.server.v1_13_R1.IBlockData;
import net.minecraft.server.v1_13_R1.IBlockState;
import net.minecraft.server.v1_13_R1.INamable;
import net.minecraft.server.v1_13_R1.MinecraftKey;
import net.minecraft.server.v1_13_R1.NBTBase;
import net.minecraft.server.v1_13_R1.NBTTagByte;
import net.minecraft.server.v1_13_R1.NBTTagByteArray;
import net.minecraft.server.v1_13_R1.NBTTagCompound;
import net.minecraft.server.v1_13_R1.NBTTagDouble;
import net.minecraft.server.v1_13_R1.NBTTagEnd;
import net.minecraft.server.v1_13_R1.NBTTagFloat;
import net.minecraft.server.v1_13_R1.NBTTagInt;
import net.minecraft.server.v1_13_R1.NBTTagIntArray;
import net.minecraft.server.v1_13_R1.NBTTagList;
import net.minecraft.server.v1_13_R1.NBTTagLong;
import net.minecraft.server.v1_13_R1.NBTTagShort;
import net.minecraft.server.v1_13_R1.NBTTagString;
import net.minecraft.server.v1_13_R1.TileEntity;
import net.minecraft.server.v1_13_R1.World;
import net.minecraft.server.v1_13_R1.WorldServer;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.block.Biome;
import org.bukkit.craftbukkit.v1_13_R1.CraftServer;
import org.bukkit.craftbukkit.v1_13_R1.CraftWorld;
import org.bukkit.craftbukkit.v1_13_R1.block.CraftBlock;
import org.bukkit.craftbukkit.v1_13_R1.entity.CraftEntity;
import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
public final class Spigot_v1_13_R1 implements BukkitImplAdapter<NBTBase> {
private final Logger logger = Logger.getLogger(getClass().getCanonicalName());
private final Field nbtListTagListField;
private final Method nbtCreateTagMethod;
// ------------------------------------------------------------------------
// Code that may break between versions of Minecraft
// ------------------------------------------------------------------------
public Spigot_v1_13_R1() throws NoSuchFieldException, NoSuchMethodException {
// A simple test
if (!Bukkit.getServer().getClass().getName().endsWith("DummyServer")) CraftServer.class.cast(Bukkit.getServer());
// test between 1.12 and 1.12.1 since md_5 didn't update revision numbers
TileEntity.class.getDeclaredMethod("load", NBTTagCompound.class);
// The list of tags on an NBTTagList
nbtListTagListField = NBTTagList.class.getDeclaredField("list");
nbtListTagListField.setAccessible(true);
// The method to create an NBTBase tag given its type ID
nbtCreateTagMethod = NBTBase.class.getDeclaredMethod("createTag", byte.class);
nbtCreateTagMethod.setAccessible(true);
}
/**
* Read the given NBT data into the given tile entity.
*
* @param tileEntity the tile entity
* @param tag the tag
*/
private static void readTagIntoTileEntity(NBTTagCompound tag, TileEntity tileEntity) {
tileEntity.load(tag);
}
/**
* Write the tile entity's NBT data to the given tag.
*
* @param tileEntity the tile entity
* @param tag the tag
*/
private static void readTileEntityIntoTag(TileEntity tileEntity, NBTTagCompound tag) {
tileEntity.save(tag);
}
/**
* Get the ID string of the given entity.
*
* @param entity the entity
* @return the entity ID or null if one is not known
*/
@Nullable
private static String getEntityId(Entity entity) {
MinecraftKey minecraftkey = EntityTypes.getName(entity.getBukkitEntity().getHandle().P());
return minecraftkey == null ? null : minecraftkey.toString();
}
/**
* Create an entity using the given entity ID.
*
* @param id the entity ID
* @param world the world
* @return an entity or null
*/
@Nullable
private static Entity createEntityFromId(String id, World world) {
return EntityTypes.a(world, new MinecraftKey(id));
}
/**
* Write the given NBT data into the given entity.
*
* @param entity the entity
* @param tag the tag
*/
private static void readTagIntoEntity(NBTTagCompound tag, Entity entity) {
entity.f(tag);
}
/**
* Write the entity's NBT data to the given tag.
*
* @param entity the entity
* @param tag the tag
*/
private static void readEntityIntoTag(Entity entity, NBTTagCompound tag) {
entity.save(tag);
}
// ------------------------------------------------------------------------
// Code that is less likely to break
// ------------------------------------------------------------------------
@Override
public int getBiomeId(Biome biome) {
BiomeBase mcBiome = CraftBlock.biomeToBiomeBase(biome);
return mcBiome != null ? BiomeBase.a(mcBiome) : 0;
}
@Override
public Biome getBiome(int id) {
BiomeBase mcBiome = BiomeBase.getBiome(id);
return CraftBlock.biomeBaseToBiome(mcBiome); // Defaults to ocean if it's an invalid ID
}
@SuppressWarnings("deprecation")
@Override
public BlockState getBlock(Location location) {
checkNotNull(location);
CraftWorld craftWorld = ((CraftWorld) location.getWorld());
int x = location.getBlockX();
int y = location.getBlockY();
int z = location.getBlockZ();
org.bukkit.block.Block bukkitBlock = location.getBlock();
BlockState state = BukkitAdapter.adapt(bukkitBlock.getBlockData());
if (state.getBlockType().getMaterial().hasContainer()) {
//Read the NBT data
TileEntity te = craftWorld.getHandle().getTileEntity(new BlockPosition(x, y, z));
if (te != null) {
NBTTagCompound tag = new NBTTagCompound();
readTileEntityIntoTag(te, tag); // Load data
return new BaseBlock(state, (CompoundTag) toNative(tag));
}
}
return state;
}
@Override
public boolean setBlock(Location location, BlockStateHolder state, boolean notifyAndLight) {
checkNotNull(location);
checkNotNull(state);
CraftWorld craftWorld = ((CraftWorld) location.getWorld());
int x = location.getBlockX();
int y = location.getBlockY();
int z = location.getBlockZ();
// Two pass update:
// Note, this will notify blocks BEFORE the tile entity is set
location.getBlock().setBlockData(BukkitAdapter.adapt(state), false);
// Copy NBT data for the block
CompoundTag nativeTag = state.getNbtData();
if (nativeTag != null) {
// We will assume that the tile entity was created for us,
// though we do not do this on the Forge version
TileEntity tileEntity = craftWorld.getHandle().getTileEntity(new BlockPosition(x, y, z));
if (tileEntity != null) {
NBTTagCompound tag = (NBTTagCompound) fromNative(nativeTag);
tag.set("x", new NBTTagInt(x));
tag.set("y", new NBTTagInt(y));
tag.set("z", new NBTTagInt(z));
readTagIntoTileEntity(tag, tileEntity); // Load data
}
}
return true;
}
@Override
public BaseEntity getEntity(org.bukkit.entity.Entity entity) {
checkNotNull(entity);
CraftEntity craftEntity = ((CraftEntity) entity);
Entity mcEntity = craftEntity.getHandle();
String id = getEntityId(mcEntity);
if (id != null) {
NBTTagCompound tag = new NBTTagCompound();
readEntityIntoTag(mcEntity, tag);
return new BaseEntity(com.sk89q.worldedit.world.entity.EntityTypes.get(id), (CompoundTag) toNative(tag));
} else {
return null;
}
}
@Nullable
@Override
public org.bukkit.entity.Entity createEntity(Location location, BaseEntity state) {
checkNotNull(location);
checkNotNull(state);
CraftWorld craftWorld = ((CraftWorld) location.getWorld());
WorldServer worldServer = craftWorld.getHandle();
Entity createdEntity = createEntityFromId(state.getType().getId(), craftWorld.getHandle());
if (createdEntity != null) {
CompoundTag nativeTag = state.getNbtData();
if (nativeTag != null) {
NBTTagCompound tag = (NBTTagCompound) fromNative(nativeTag);
for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) {
tag.remove(name);
}
readTagIntoEntity(tag, createdEntity);
}
createdEntity.setLocation(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
worldServer.addEntity(createdEntity, SpawnReason.CUSTOM);
return createdEntity.getBukkitEntity();
} else {
return null;
}
}
@SuppressWarnings("unchecked")
@Override
public Map<String, ? extends Property> getProperties(BlockType blockType) {
Block block;
try {
block = Block.getByName(blockType.getId());
} catch (Throwable e) {
return Collections.emptyMap();
}
if (block == null) {
logger.warning("Failed to find properties for " + blockType.getId());
return Collections.emptyMap();
}
Map<String, Property> properties = Maps.newLinkedHashMap();
BlockStateList<Block, IBlockData> blockStateList = block.getStates();
for (IBlockState state : blockStateList.d()) {
Property property;
if (state instanceof BlockStateBoolean) {
property = new BooleanProperty(state.a(), ImmutableList.copyOf(state.d()));
} else if (state instanceof BlockStateDirection) {
property = new DirectionalProperty(state.a(),
(List<Direction>) state.d().stream().map(e -> Direction.valueOf(((INamable) e).getName().toUpperCase())).collect(Collectors.toList()));
} else if (state instanceof BlockStateEnum) {
property = new EnumProperty(state.a(),
(List<String>) state.d().stream().map(e -> ((INamable) e).getName()).collect(Collectors.toList()));
} else if (state instanceof BlockStateInteger) {
property = new IntegerProperty(state.a(), ImmutableList.copyOf(state.d()));
} else {
throw new IllegalArgumentException("WorldEdit needs an update to support " + state.getClass().getSimpleName());
}
properties.put(property.getName(), property);
}
return properties;
}
/**
* Converts from a non-native NMS NBT structure to a native WorldEdit NBT
* structure.
*
* @param foreign non-native NMS NBT structure
* @return native WorldEdit NBT structure
*/
@SuppressWarnings("unchecked")
@Override
public Tag toNative(NBTBase foreign) {
if (foreign == null) {
return null;
}
if (foreign instanceof NBTTagCompound) {
Map<String, Tag> values = new HashMap<>();
Set<String> foreignKeys = ((NBTTagCompound) foreign).getKeys(); // map.keySet
for (String str : foreignKeys) {
NBTBase base = ((NBTTagCompound) foreign).get(str);
values.put(str, toNative(base));
}
return new CompoundTag(values);
} else if (foreign instanceof NBTTagByte) {
return new ByteTag(((NBTTagByte) foreign).g()); // getByte
} else if (foreign instanceof NBTTagByteArray) {
return new ByteArrayTag(((NBTTagByteArray) foreign).c()); // data
} else if (foreign instanceof NBTTagDouble) {
return new DoubleTag(((NBTTagDouble) foreign).asDouble()); // getDouble
} else if (foreign instanceof NBTTagFloat) {
return new FloatTag(((NBTTagFloat) foreign).i()); // getFloat
} else if (foreign instanceof NBTTagInt) {
return new IntTag(((NBTTagInt) foreign).e()); // getInt
} else if (foreign instanceof NBTTagIntArray) {
return new IntArrayTag(((NBTTagIntArray) foreign).d()); // data
} else if (foreign instanceof NBTTagList) {
try {
return toNativeList((NBTTagList) foreign);
} catch (Throwable e) {
logger.log(Level.WARNING, "Failed to convert NBTTagList", e);
return new ListTag(ByteTag.class, new ArrayList<ByteTag>());
}
} else if (foreign instanceof NBTTagLong) {
return new LongTag(((NBTTagLong) foreign).d()); // getLong
} else if (foreign instanceof NBTTagShort) {
return new ShortTag(((NBTTagShort) foreign).f()); // getShort
} else if (foreign instanceof NBTTagString) {
return new StringTag(foreign.b_()); // data
} else if (foreign instanceof NBTTagEnd) {
return new EndTag();
} else {
throw new IllegalArgumentException("Don't know how to make native " + foreign.getClass().getCanonicalName());
}
}
/**
* Convert a foreign NBT list tag into a native WorldEdit one.
*
* @param foreign the foreign tag
* @return the converted tag
* @throws NoSuchFieldException on error
* @throws SecurityException on error
* @throws IllegalArgumentException on error
* @throws IllegalAccessException on error
*/
public ListTag toNativeList(NBTTagList foreign) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
List<Tag> values = new ArrayList<>();
int type = foreign.getTypeId();
List foreignList;
foreignList = (List) nbtListTagListField.get(foreign);
for (int i = 0; i < foreign.size(); i++) {
NBTBase element = (NBTBase) foreignList.get(i);
values.add(toNative(element)); // List elements shouldn't have names
}
Class<? extends Tag> cls = NBTConstants.getClassFromType(type);
return new ListTag(cls, values);
}
/**
* Converts a WorldEdit-native NBT structure to a NMS structure.
*
* @param foreign structure to convert
* @return non-native structure
*/
@Override
public NBTBase fromNative(Tag foreign) {
if (foreign == null) {
return null;
}
if (foreign instanceof CompoundTag) {
NBTTagCompound tag = new NBTTagCompound();
for (Map.Entry<String, Tag> entry : ((CompoundTag) foreign)
.getValue().entrySet()) {
tag.set(entry.getKey(), fromNative(entry.getValue()));
}
return tag;
} else if (foreign instanceof ByteTag) {
return new NBTTagByte(((ByteTag) foreign).getValue());
} else if (foreign instanceof ByteArrayTag) {
return new NBTTagByteArray(((ByteArrayTag) foreign).getValue());
} else if (foreign instanceof DoubleTag) {
return new NBTTagDouble(((DoubleTag) foreign).getValue());
} else if (foreign instanceof FloatTag) {
return new NBTTagFloat(((FloatTag) foreign).getValue());
} else if (foreign instanceof IntTag) {
return new NBTTagInt(((IntTag) foreign).getValue());
} else if (foreign instanceof IntArrayTag) {
return new NBTTagIntArray(((IntArrayTag) foreign).getValue());
} else if (foreign instanceof ListTag) {
NBTTagList tag = new NBTTagList();
ListTag<?> foreignList = (ListTag) foreign;
for (Tag t : foreignList.getValue()) {
tag.add(fromNative(t));
}
return tag;
} else if (foreign instanceof LongTag) {
return new NBTTagLong(((LongTag) foreign).getValue());
} else if (foreign instanceof ShortTag) {
return new NBTTagShort(((ShortTag) foreign).getValue());
} else if (foreign instanceof StringTag) {
return new NBTTagString(((StringTag) foreign).getValue());
} else if (foreign instanceof EndTag) {
try {
return (NBTBase) nbtCreateTagMethod.invoke(null, (byte) 0);
} catch (Exception e) {
return null;
}
} else {
throw new IllegalArgumentException("Don't know how to make NMS " + foreign.getClass().getCanonicalName());
}
}
}