Merge master again

This commit is contained in:
Kenzie Togami 2019-04-26 01:25:42 -07:00
commit 7ff537138a
No known key found for this signature in database
GPG Key ID: 5D200B325E157A81
36 changed files with 1088 additions and 158 deletions

View File

@ -67,6 +67,14 @@ public class BukkitServerInterface implements MultiUserPlatform {
return BukkitRegistries.getInstance();
}
@Override
public int getDataVersion() {
if (plugin.getBukkitImplAdapter() != null) {
return plugin.getBukkitImplAdapter().getDataVersion();
}
return 0;
}
@Override
public boolean isValidMobType(String type) {
final EntityType entityType = EntityType.fromName(type);

View File

@ -40,6 +40,13 @@ import javax.annotation.Nullable;
*/
public interface BukkitImplAdapter {
/**
* Get the Minecraft data version for the current world data.
*
* @return the data version
*/
int getDataVersion();
/**
* Get the block at the given location.
*

View File

@ -181,6 +181,18 @@ public class CompoundTagBuilder {
return put(key, new StringTag(value));
}
/**
* Remove the given key from the compound tag. Does nothing if the key doesn't exist.
*
* @param key the key
* @return this object
*/
public CompoundTagBuilder remove(String key) {
checkNotNull(key);
entries.remove(key);
return this;
}
/**
* Put all the entries from the given map into this map.
*

View File

@ -59,6 +59,7 @@ import com.sk89q.worldedit.world.block.BlockTypes;
import org.enginehub.piston.annotation.Command;
import org.enginehub.piston.annotation.CommandContainer;
import org.enginehub.piston.annotation.param.Arg;
import org.enginehub.piston.annotation.param.ArgFlag;
import org.enginehub.piston.annotation.param.Switch;
import static com.google.common.base.Preconditions.checkNotNull;
@ -145,8 +146,14 @@ public class BrushCommands {
public void clipboardBrush(Player player, LocalSession session,
@Switch(name = 'a', desc = "Don't paste air from the clipboard")
boolean ignoreAir,
@Switch(name = 'p', desc = "Paste using clipboard origin, instead of being centered at the target location")
boolean usingOrigin) throws WorldEditException {
@Switch(name = 'o', desc = "Paste using clipboard origin, instead of being centered at the target location")
boolean usingOrigin,
@Switch(name = 'e', desc = "Paste entities if available")
boolean pasteEntities,
@Switch(name = 'b', desc = "Paste biomes if available")
boolean pasteBiomes,
@ArgFlag(name = 'm', desc = "Skip blocks matching this mask", def = "")
Mask sourceMask) throws WorldEditException {
ClipboardHolder holder = session.getClipboard();
Clipboard clipboard = holder.getClipboard();
@ -157,7 +164,7 @@ public class BrushCommands {
worldEdit.checkMaxBrushRadius(size.getBlockZ() / 2D - 1);
BrushTool tool = session.getBrushTool(player.getItemInHand(HandSide.MAIN_HAND).getType());
tool.setBrush(new ClipboardBrush(holder, ignoreAir, usingOrigin), "worldedit.brush.clipboard");
tool.setBrush(new ClipboardBrush(holder, ignoreAir, usingOrigin, pasteEntities, pasteBiomes, sourceMask), "worldedit.brush.clipboard");
player.print("Clipboard brush shape equipped.");
}

View File

@ -19,6 +19,7 @@
package com.sk89q.worldedit.command;
import com.google.common.collect.Lists;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.WorldEditException;
@ -49,6 +50,8 @@ import org.enginehub.piston.annotation.param.Arg;
import org.enginehub.piston.annotation.param.ArgFlag;
import org.enginehub.piston.annotation.param.Switch;
import java.util.List;
import static com.sk89q.worldedit.command.util.Logging.LogMode.PLACEMENT;
import static com.sk89q.worldedit.command.util.Logging.LogMode.REGION;
@ -73,19 +76,24 @@ public class ClipboardCommands {
@Selection Region region,
@Switch(name = 'e', desc = "Also copy entities")
boolean copyEntities,
@Switch(name = 'b', desc = "Also copy biomes")
boolean copyBiomes,
@ArgFlag(name = 'm', desc = "Set the exclude mask, matching blocks become air", def = "")
Mask mask) throws WorldEditException {
BlockArrayClipboard clipboard = new BlockArrayClipboard(region);
clipboard.setOrigin(session.getPlacementPosition(player));
ForwardExtentCopy copy = new ForwardExtentCopy(editSession, region, clipboard, region.getMinimumPoint());
copy.setCopyingEntities(copyEntities);
copy.setCopyingBiomes(copyBiomes);
if (mask != null) {
copy.setSourceMask(mask);
}
Operations.completeLegacy(copy);
session.setClipboard(new ClipboardHolder(clipboard));
player.print(region.getArea() + " block(s) were copied.");
List<String> messages = Lists.newArrayList();
copy.addStatusMessages(messages);
messages.forEach(player::print);
}
@Command(
@ -101,6 +109,8 @@ public class ClipboardCommands {
Pattern leavePattern,
@Switch(name = 'e', desc = "Also cut entities")
boolean copyEntities,
@Switch(name = 'b', desc = "Also copy biomes, source biomes are unaffected")
boolean copyBiomes,
@ArgFlag(name = 'm', desc = "Set the exclude mask, matching blocks become air", def = "")
Mask mask) throws WorldEditException {
@ -110,13 +120,16 @@ public class ClipboardCommands {
copy.setSourceFunction(new BlockReplace(editSession, leavePattern));
copy.setCopyingEntities(copyEntities);
copy.setRemovingEntities(true);
copy.setCopyingBiomes(copyBiomes);
if (mask != null) {
copy.setSourceMask(mask);
}
Operations.completeLegacy(copy);
session.setClipboard(new ClipboardHolder(clipboard));
player.print(region.getArea() + " block(s) were cut.");
List<String> messages = Lists.newArrayList();
copy.addStatusMessages(messages);
messages.forEach(player::print);
}
@Command(
@ -131,7 +144,13 @@ public class ClipboardCommands {
@Switch(name = 'o', desc = "Paste at the original position")
boolean atOrigin,
@Switch(name = 's', desc = "Select the region after pasting")
boolean selectPasted) throws WorldEditException {
boolean selectPasted,
@Switch(name = 'e', desc = "Paste entities if available")
boolean pasteEntities,
@Switch(name = 'b', desc = "Paste biomes if available")
boolean pasteBiomes,
@ArgFlag(name = 'm', desc = "Skip blocks matching this mask", def = "")
Mask sourceMask) throws WorldEditException {
ClipboardHolder holder = session.getClipboard();
Clipboard clipboard = holder.getClipboard();
@ -139,10 +158,13 @@ public class ClipboardCommands {
BlockVector3 to = atOrigin ? clipboard.getOrigin() : session.getPlacementPosition(player);
Operation operation = holder
.createPaste(editSession)
.to(to)
.ignoreAirBlocks(ignoreAirBlocks)
.build();
.createPaste(editSession)
.to(to)
.ignoreAirBlocks(ignoreAirBlocks)
.copyBiomes(pasteBiomes)
.copyEntities(pasteEntities)
.maskSource(sourceMask)
.build();
Operations.completeLegacy(operation);
if (selectPasted) {
@ -156,6 +178,9 @@ public class ClipboardCommands {
}
player.print("The clipboard has been pasted at " + to);
List<String> messages = Lists.newArrayList();
operation.addStatusMessages(messages);
messages.forEach(player::print);
}
@Command(

View File

@ -115,6 +115,9 @@ class FlattenedClipboardTransform {
BlockTransformExtent extent = new BlockTransformExtent(original, transform);
ForwardExtentCopy copy = new ForwardExtentCopy(extent, original.getRegion(), original.getOrigin(), target, original.getOrigin());
copy.setTransform(transform);
if (original.hasBiomes()) {
copy.setCopyingBiomes(true);
}
return copy;
}

View File

@ -22,6 +22,7 @@ package com.sk89q.worldedit.command.tool.brush;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.MaxChangedBlocksException;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.function.operation.Operation;
import com.sk89q.worldedit.function.operation.Operations;
import com.sk89q.worldedit.function.pattern.Pattern;
@ -31,14 +32,30 @@ import com.sk89q.worldedit.session.ClipboardHolder;
public class ClipboardBrush implements Brush {
private ClipboardHolder holder;
private boolean ignoreAirBlocks;
private boolean usingOrigin;
private final ClipboardHolder holder;
private final boolean ignoreAirBlocks;
private final boolean usingOrigin;
private final boolean pasteEntities;
private final boolean pasteBiomes;
private final Mask sourceMask;
public ClipboardBrush(ClipboardHolder holder, boolean ignoreAirBlocks, boolean usingOrigin) {
this.holder = holder;
this.ignoreAirBlocks = ignoreAirBlocks;
this.usingOrigin = usingOrigin;
this.pasteBiomes = false;
this.pasteEntities = false;
this.sourceMask = null;
}
public ClipboardBrush(ClipboardHolder holder, boolean ignoreAirBlocks, boolean usingOrigin, boolean pasteEntities,
boolean pasteBiomes, Mask sourceMask) {
this.holder = holder;
this.ignoreAirBlocks = ignoreAirBlocks;
this.usingOrigin = usingOrigin;
this.pasteEntities = pasteEntities;
this.pasteBiomes = pasteBiomes;
this.sourceMask = sourceMask;
}
@Override
@ -51,6 +68,9 @@ public class ClipboardBrush implements Brush {
.createPaste(editSession)
.to(usingOrigin ? position : position.subtract(centerOffset))
.ignoreAirBlocks(ignoreAirBlocks)
.copyEntities(pasteEntities)
.copyBiomes(pasteBiomes)
.maskSource(sourceMask)
.build();
Operations.completeLegacy(operation);

View File

@ -46,6 +46,13 @@ public interface Platform {
*/
Registries getRegistries();
/**
* Gets the Minecraft data version being used by the platform.
*
* @return the data version
*/
int getDataVersion();
/**
* Checks if a mob type is valid.
*

View File

@ -161,12 +161,20 @@ public class BlockArrayClipboard implements Clipboard {
}
}
@Override
public boolean hasBiomes() {
return biomes != null;
}
@Override
public BiomeType getBiome(BlockVector2 position) {
if (biomes != null
&& position.containedWithin(getMinimumPoint().toBlockVector2(), getMaximumPoint().toBlockVector2())) {
BlockVector2 v = position.subtract(region.getMinimumPoint().toBlockVector2());
return biomes[v.getBlockX()][v.getBlockZ()];
BiomeType biomeType = biomes[v.getBlockX()][v.getBlockZ()];
if (biomeType != null) {
return biomeType;
}
}
return BiomeTypes.OCEAN;

View File

@ -20,6 +20,7 @@
package com.sk89q.worldedit.extent.clipboard;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.math.BlockVector2;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.regions.Region;
@ -58,4 +59,15 @@ public interface Clipboard extends Extent {
*/
void setOrigin(BlockVector3 origin);
/**
* Returns true if the clipboard has biome data. This can be checked since {@link Extent#getBiome(BlockVector2)}
* strongly suggests returning {@link com.sk89q.worldedit.world.biome.BiomeTypes.OCEAN} instead of {@code null}
* if biomes aren't present. However, it might not be desired to set areas to ocean if the clipboard is defaulting
* to ocean, instead of having biomes explicitly set.
*
* @return true if the clipboard has biome data set
*/
default boolean hasBiomes() {
return false;
}
}

View File

@ -19,6 +19,7 @@
package com.sk89q.worldedit.extent.clipboard.io;
import com.google.common.collect.ImmutableList;
import com.sk89q.jnbt.ByteArrayTag;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.IntTag;
@ -32,8 +33,13 @@ import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.extent.clipboard.io.legacycompat.EntityNBTCompatibilityHandler;
import com.sk89q.worldedit.extent.clipboard.io.legacycompat.FlowerPotCompatibilityHandler;
import com.sk89q.worldedit.extent.clipboard.io.legacycompat.NBTCompatibilityHandler;
import com.sk89q.worldedit.extent.clipboard.io.legacycompat.NoteBlockCompatibilityHandler;
import com.sk89q.worldedit.extent.clipboard.io.legacycompat.Pre13HangingCompatibilityHandler;
import com.sk89q.worldedit.extent.clipboard.io.legacycompat.SignCompatibilityHandler;
import com.sk89q.worldedit.extent.clipboard.io.legacycompat.SkullBlockCompatibilityHandler;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.regions.CuboidRegion;
import com.sk89q.worldedit.regions.Region;
@ -47,10 +53,11 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.google.common.base.Preconditions.checkNotNull;
@ -59,12 +66,18 @@ import static com.google.common.base.Preconditions.checkNotNull;
*/
public class MCEditSchematicReader extends NBTSchematicReader {
private static final List<NBTCompatibilityHandler> COMPATIBILITY_HANDLERS = new ArrayList<>();
static {
COMPATIBILITY_HANDLERS.add(new SignCompatibilityHandler());
// TODO Add a handler for skulls, flower pots, note blocks, etc.
}
private static final ImmutableList<NBTCompatibilityHandler> COMPATIBILITY_HANDLERS
= ImmutableList.of(
new SignCompatibilityHandler(),
new FlowerPotCompatibilityHandler(),
new NoteBlockCompatibilityHandler(),
new SkullBlockCompatibilityHandler()
// TODO - item tags for inventories...? DFUs :>
);
private static final ImmutableList<EntityNBTCompatibilityHandler> ENTITY_COMPATIBILITY_HANDLERS
= ImmutableList.of(
new Pre13HangingCompatibilityHandler()
);
private static final Logger log = LoggerFactory.getLogger(MCEditSchematicReader.class);
private final NBTInputStream inputStream;
@ -162,65 +175,55 @@ public class MCEditSchematicReader extends NBTSchematicReader {
// Need to pull out tile entities
List<Tag> tileEntities = requireTag(schematic, "TileEntities", ListTag.class).getValue();
Map<BlockVector3, Map<String, Tag>> tileEntitiesMap = new HashMap<>();
Map<BlockVector3, BlockState> blockOverrides = new HashMap<>();
for (Tag tag : tileEntities) {
if (!(tag instanceof CompoundTag)) continue;
CompoundTag t = (CompoundTag) tag;
int x = 0;
int y = 0;
int z = 0;
int x = t.getInt("x");
int y = t.getInt("y");
int z = t.getInt("z");
String id = t.getString("id");
Map<String, Tag> values = new HashMap<>();
for (Map.Entry<String, Tag> entry : t.getValue().entrySet()) {
switch (entry.getKey()) {
case "x":
if (entry.getValue() instanceof IntTag) {
x = ((IntTag) entry.getValue()).getValue();
}
break;
case "y":
if (entry.getValue() instanceof IntTag) {
y = ((IntTag) entry.getValue()).getValue();
}
break;
case "z":
if (entry.getValue() instanceof IntTag) {
z = ((IntTag) entry.getValue()).getValue();
}
break;
}
values.put(entry.getKey(), entry.getValue());
}
Map<String, Tag> values = new HashMap<>(t.getValue());
values.put("id", new StringTag(convertBlockEntityId(id)));
int index = y * width * length + z * width + x;
BlockState block = LegacyMapper.getInstance().getBlockFromLegacy(blocks[index], blockData[index]);
if (block != null) {
BlockState newBlock = block;
if (newBlock != null) {
for (NBTCompatibilityHandler handler : COMPATIBILITY_HANDLERS) {
if (handler.isAffectedBlock(block)) {
handler.updateNBT(block, values);
if (handler.isAffectedBlock(newBlock)) {
newBlock = handler.updateNBT(block, values);
if (newBlock == null) {
break;
}
}
}
}
BlockVector3 vec = BlockVector3.at(x, y, z);
tileEntitiesMap.put(vec, values);
if (newBlock != block) {
blockOverrides.put(vec, newBlock);
}
}
BlockArrayClipboard clipboard = new BlockArrayClipboard(region);
clipboard.setOrigin(origin);
// Don't log a torrent of errors
int failedBlockSets = 0;
Set<Integer> unknownBlocks = new HashSet<>();
for (int x = 0; x < width; ++x) {
for (int y = 0; y < height; ++y) {
for (int z = 0; z < length; ++z) {
int index = y * width * length + z * width + x;
BlockVector3 pt = BlockVector3.at(x, y, z);
BlockState state = LegacyMapper.getInstance().getBlockFromLegacy(blocks[index], blockData[index]);
boolean useOverride = blockOverrides.containsKey(pt);
BlockState state = useOverride
? blockOverrides.get(pt)
: LegacyMapper.getInstance().getBlockFromLegacy(blocks[index], blockData[index]);
try {
if (state != null) {
@ -229,21 +232,16 @@ public class MCEditSchematicReader extends NBTSchematicReader {
} else {
clipboard.setBlock(region.getMinimumPoint().add(pt), state);
}
} else {
log.warn("Unknown block when pasting schematic: " + blocks[index] + ":" + blockData[index] + ". Please report this issue.");
} else if (!useOverride) {
short block = blocks[index];
byte data = blockData[index];
int combined = block << 8 | data;
if (unknownBlocks.add(combined)) {
log.warn("Unknown block when pasting schematic: "
+ block + ":" + data + ". Please report this issue.");
}
}
} catch (WorldEditException e) {
switch (failedBlockSets) {
case 0:
log.warn("Failed to set block on a Clipboard", e);
break;
case 1:
log.warn("Failed to set block on a Clipboard (again) -- no more messages will be logged", e);
break;
default:
}
failedBlockSets++;
} catch (WorldEditException ignored) { // BlockArrayClipboard won't throw this
}
}
}
@ -253,8 +251,9 @@ public class MCEditSchematicReader extends NBTSchematicReader {
// Entities
// ====================================================================
List<Tag> entityTags = getTag(schematic, "Entities", ListTag.class).getValue();
if (entityTags != null) {
ListTag entityList = getTag(schematic, "Entities", ListTag.class);
if (entityList != null) {
List<Tag> entityTags = entityList.getValue();
for (Tag tag : entityTags) {
if (tag instanceof CompoundTag) {
CompoundTag compound = (CompoundTag) tag;
@ -264,6 +263,11 @@ public class MCEditSchematicReader extends NBTSchematicReader {
if (!id.isEmpty()) {
EntityType entityType = EntityTypes.get(id.toLowerCase());
if (entityType != null) {
for (EntityNBTCompatibilityHandler compatibilityHandler : ENTITY_COMPATIBILITY_HANDLERS) {
if (compatibilityHandler.isAffectedEntity(entityType, compound)) {
compound = compatibilityHandler.updateNBT(entityType, compound);
}
}
BaseEntity state = new BaseEntity(entityType, compound);
clipboard.createEntity(location, state);
} else {
@ -279,32 +283,66 @@ public class MCEditSchematicReader extends NBTSchematicReader {
private String convertEntityId(String id) {
switch(id) {
case "xp_orb":
return "experience_orb";
case "xp_bottle":
return "experience_bottle";
case "eye_of_ender_signal":
return "eye_of_ender";
case "ender_crystal":
return "end_crystal";
case "fireworks_rocket":
return "firework_rocket";
case "commandblock_minecart":
return "command_block_minecart";
case "snowman":
return "snow_golem";
case "villager_golem":
return "iron_golem";
case "evocation_fangs":
return "evoker_fangs";
case "evocation_illager":
return "evoker";
case "vindication_illager":
return "vindicator";
case "illusion_illager":
return "illusioner";
case "AreaEffectCloud": return "area_effect_cloud";
case "ArmorStand": return "armor_stand";
case "CaveSpider": return "cave_spider";
case "MinecartChest": return "chest_minecart";
case "MinecartCommandBlock": return "commandblock_minecart";
case "DragonFireball": return "dragon_fireball";
case "ThrownEgg": return "egg";
case "EnderCrystal": return "ender_crystal";
case "EnderDragon": return "ender_dragon";
case "ThrownEnderpearl": return "ender_pearl";
case "EyeOfEnderSignal": return "eye_of_ender_signal";
case "FallingSand": return "falling_block";
case "FireworksRocketEntity": return "fireworks_rocket";
case "MinecartFurnace": return "furnace_minecart";
case "MinecartHopper": return "hopper_minecart";
case "EntityHorse": return "horse";
case "ItemFrame": return "item_frame";
case "LeashKnot": return "leash_knot";
case "LightningBolt": return "lightning_bolt";
case "LavaSlime": return "magma_cube";
case "MinecartRideable": return "minecart";
case "MushroomCow": return "mooshroom";
case "Ozelot": return "ocelot";
case "PolarBear": return "polar_bear";
case "ThrownPotion": return "potion";
case "ShulkerBullet": return "shulker_bullet";
case "SmallFireball": return "small_fireball";
case "MinecartSpawner": return "spawner_minecart";
case "SpectralArrow": return "spectral_arrow";
case "PrimedTnt": return "tnt";
case "MinecartTNT": return "tnt_minecart";
case "VillagerGolem": return "villager_golem";
case "WitherBoss": return "wither";
case "WitherSkull": return "wither_skull";
case "ThrownExpBottle": return "xp_bottle";
case "XPOrb": return "xp_orb";
case "PigZombie": return "zombie_pigman";
default: return id;
}
}
private String convertBlockEntityId(String id) {
switch(id) {
case "Cauldron": return "brewing_stand";
case "Control": return "command_block";
case "DLDetector": return "daylight_detector";
case "Trap": return "dispenser";
case "EnchantTable": return "enchanting_table";
case "EndGateway": return "end_gateway";
case "AirPortal": return "end_portal";
case "EnderChest": return "ender_chest";
case "FlowerPot": return "flower_pot";
case "RecordPlayer": return "jukebox";
case "MobSpawner": return "mob_spawner";
case "Music":
case "noteblock":
return "note_block";
case "Structure": return "structure_block";
default: return id;
}
return id;
}
@Override

View File

@ -28,18 +28,28 @@ import com.sk89q.jnbt.ListTag;
import com.sk89q.jnbt.NBTInputStream;
import com.sk89q.jnbt.NamedTag;
import com.sk89q.jnbt.ShortTag;
import com.sk89q.jnbt.StringTag;
import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.extension.input.InputParseException;
import com.sk89q.worldedit.extension.input.ParserContext;
import com.sk89q.worldedit.extension.platform.Capability;
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.extent.clipboard.io.legacycompat.NBTCompatibilityHandler;
import com.sk89q.worldedit.math.BlockVector2;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.regions.CuboidRegion;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.util.Location;
import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.biome.BiomeTypes;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.entity.EntityType;
import com.sk89q.worldedit.world.entity.EntityTypes;
import com.sk89q.worldedit.world.storage.NBTConversions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -48,6 +58,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkNotNull;
@ -89,6 +100,15 @@ public class SpongeSchematicReader extends NBTSchematicReader {
int version = requireTag(schematic, "Version", IntTag.class).getValue();
if (version == 1) {
return readVersion1(schematicTag);
} else if (version == 2) {
int dataVersion = requireTag(schematic, "DataVersion", IntTag.class).getValue();
if (dataVersion > WorldEdit.getInstance().getPlatformManager()
.queryCapability(Capability.WORLD_EDITING).getDataVersion()) {
// maybe should just warn? simple schematics may be compatible still.
throw new IOException("Schematic was made in a newer Minecraft version. Data may be incompatible.");
}
BlockArrayClipboard clip = readVersion1(schematicTag);
return readVersion2(clip, schematicTag);
}
throw new IOException("This schematic version is currently not supported");
}
@ -126,7 +146,7 @@ public class SpongeSchematicReader extends NBTSchematicReader {
int paletteMax = requireTag(schematic, "PaletteMax", IntTag.class).getValue();
Map<String, Tag> paletteObject = requireTag(schematic, "Palette", CompoundTag.class).getValue();
if (paletteObject.size() != paletteMax) {
throw new IOException("Differing given palette size to actual size");
throw new IOException("Block palette size does not match expected size.");
}
Map<Integer, BlockState> palette = new HashMap<>();
@ -142,7 +162,8 @@ public class SpongeSchematicReader extends NBTSchematicReader {
try {
state = WorldEdit.getInstance().getBlockFactory().parseFromInput(palettePart, parserContext).toImmutableState();
} catch (InputParseException e) {
throw new IOException("Invalid BlockState in schematic: " + palettePart + ". Are you missing a mod or using a schematic made in a newer version of Minecraft?");
throw new IOException("Invalid BlockState in palette: " + palettePart +
". Are you missing a mod or using a schematic made in a newer version of Minecraft?");
}
palette.put(id, state);
}
@ -168,8 +189,8 @@ public class SpongeSchematicReader extends NBTSchematicReader {
int index = 0;
int i = 0;
int value = 0;
int varintLength = 0;
int value;
int varintLength;
while (i < blocks.length) {
value = 0;
varintLength = 0;
@ -185,7 +206,7 @@ public class SpongeSchematicReader extends NBTSchematicReader {
}
i++;
}
// index = (y * length + z) * width + x
// index = (y * length * width) + (z * width) + x
int y = index / (width * length);
int z = (index % (width * length)) / width;
int x = (index % (width * length)) % width;
@ -219,6 +240,99 @@ public class SpongeSchematicReader extends NBTSchematicReader {
return clipboard;
}
private Clipboard readVersion2(BlockArrayClipboard version1, CompoundTag schematicTag) throws IOException {
Map<String, Tag> schematic = schematicTag.getValue();
if (schematic.containsKey("BiomeData")) {
readBiomes(version1, schematic);
}
if (schematic.containsKey("Entities")) {
readEntities(version1, schematic);
}
return version1;
}
private void readBiomes(BlockArrayClipboard clipboard, Map<String, Tag> schematic) throws IOException {
ByteArrayTag dataTag = requireTag(schematic, "BiomeData", ByteArrayTag.class);
IntTag maxTag = requireTag(schematic, "BiomePaletteMax", IntTag.class);
CompoundTag paletteTag = requireTag(schematic, "BiomePalette", CompoundTag.class);
Map<Integer, BiomeType> palette = new HashMap<>();
if (maxTag.getValue() != paletteTag.getValue().size()) {
throw new IOException("Biome palette size does not match expected size.");
}
Map<String, Tag> paletteEntries = paletteTag.getValue();
for (Entry<String, Tag> palettePart : paletteEntries.entrySet()) {
BiomeType biome = BiomeTypes.get(palettePart.getKey());
if (biome == null) {
log.warn("Unknown biome type :" + palettePart.getKey() +
" in palette. Are you missing a mod or using a schematic made in a newer version of Minecraft?");
}
Tag idTag = palettePart.getValue();
if (!(idTag instanceof IntTag)) {
throw new IOException("Biome mapped to non-Int tag.");
}
palette.put(((IntTag) idTag).getValue(), biome);
}
int width = clipboard.getDimensions().getX();
byte[] biomes = dataTag.getValue();
int biomeIndex = 0;
int biomeJ = 0;
int bVal;
int varIntLength;
BlockVector2 min = clipboard.getMinimumPoint().toBlockVector2();
while (biomeJ < biomes.length) {
bVal = 0;
varIntLength = 0;
while (true) {
bVal |= (biomes[biomeJ] & 127) << (varIntLength++ * 7);
if (varIntLength > 5) {
throw new RuntimeException("VarInt too big (probably corrupted data)");
}
if (((biomes[biomeJ] & 128) != 128)) {
biomeJ++;
break;
}
biomeJ++;
}
int z = biomeIndex / width;
int x = biomeIndex % width;
BiomeType type = palette.get(bVal);
clipboard.setBiome(min.add(x, z), type);
biomeIndex++;
}
}
private void readEntities(BlockArrayClipboard clipboard, Map<String, Tag> schematic) throws IOException {
List<Tag> entList = requireTag(schematic, "Entities", ListTag.class).getValue();
if (entList.isEmpty()) {
return;
}
for (Tag et : entList) {
if (!(et instanceof CompoundTag)) {
continue;
}
CompoundTag entityTag = (CompoundTag) et;
Map<String, Tag> tags = entityTag.getValue();
String id = requireTag(tags, "Id", StringTag.class).getValue();
EntityType entityType = EntityTypes.get(id);
if (entityType != null) {
Location location = NBTConversions.toLocation(clipboard,
requireTag(tags, "Pos", ListTag.class),
requireTag(tags, "Rotation", ListTag.class));
BaseEntity state = new BaseEntity(entityType,
entityTag.createBuilder().putString("id", id).remove("Id").build());
clipboard.createEntity(location, state);
} else {
log.warn("Unknown entity when pasting schematic: " + id);
}
}
}
@Override
public void close() throws IOException {
inputStream.close();

View File

@ -21,8 +21,12 @@ package com.sk89q.worldedit.extent.clipboard.io;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.sk89q.jnbt.ByteArrayTag;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.DoubleTag;
import com.sk89q.jnbt.FloatTag;
import com.sk89q.jnbt.IntArrayTag;
import com.sk89q.jnbt.IntTag;
import com.sk89q.jnbt.ListTag;
@ -30,9 +34,16 @@ import com.sk89q.jnbt.NBTOutputStream;
import com.sk89q.jnbt.ShortTag;
import com.sk89q.jnbt.StringTag;
import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.extension.platform.Capability;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.math.BlockVector2;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.math.Vector3;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.util.Location;
import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.block.BaseBlock;
import java.io.ByteArrayOutputStream;
@ -41,12 +52,15 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* Writes schematic files using the Sponge schematic format.
*/
public class SpongeSchematicWriter implements ClipboardWriter {
private static final int CURRENT_VERSION = 2;
private static final int MAX_SIZE = Short.MAX_VALUE - Short.MIN_VALUE;
private final NBTOutputStream outputStream;
@ -63,17 +77,17 @@ public class SpongeSchematicWriter implements ClipboardWriter {
@Override
public void write(Clipboard clipboard) throws IOException {
// For now always write the latest version. Maybe provide support for earlier if more appear.
outputStream.writeNamedTag("Schematic", new CompoundTag(write1(clipboard)));
outputStream.writeNamedTag("Schematic", new CompoundTag(write2(clipboard)));
}
/**
* Writes a version 1 schematic file.
* Writes a version 2 schematic file.
*
* @param clipboard The clipboard
* @return The schematic map
* @throws IOException If an error occurs
*/
private Map<String, Tag> write1(Clipboard clipboard) throws IOException {
private Map<String, Tag> write2(Clipboard clipboard) throws IOException {
Region region = clipboard.getRegion();
BlockVector3 origin = clipboard.getOrigin();
BlockVector3 min = region.getMinimumPoint();
@ -93,7 +107,9 @@ public class SpongeSchematicWriter implements ClipboardWriter {
}
Map<String, Tag> schematic = new HashMap<>();
schematic.put("Version", new IntTag(1));
schematic.put("Version", new IntTag(CURRENT_VERSION));
schematic.put("DataVersion", new IntTag(
WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.WORLD_EDITING).getDataVersion()));
Map<String, Tag> metadata = new HashMap<>();
metadata.put("WEOffsetX", new IntTag(offset.getBlockX()));
@ -129,10 +145,7 @@ public class SpongeSchematicWriter implements ClipboardWriter {
BlockVector3 point = BlockVector3.at(x0, y0, z0);
BaseBlock block = clipboard.getFullBlock(point);
if (block.getNbtData() != null) {
Map<String, Tag> values = new HashMap<>();
for (Map.Entry<String, Tag> entry : block.getNbtData().getValue().entrySet()) {
values.put(entry.getKey(), entry.getValue());
}
Map<String, Tag> values = new HashMap<>(block.getNbtData().getValue());
values.remove("id"); // Remove 'id' if it exists. We want 'Id'
@ -179,9 +192,101 @@ public class SpongeSchematicWriter implements ClipboardWriter {
schematic.put("BlockData", new ByteArrayTag(buffer.toByteArray()));
schematic.put("TileEntities", new ListTag(CompoundTag.class, tileEntities));
// version 2 stuff
if (clipboard.hasBiomes()) {
writeBiomes(clipboard, schematic);
}
if (!clipboard.getEntities().isEmpty()) {
writeEntities(clipboard, schematic);
}
return schematic;
}
private void writeBiomes(Clipboard clipboard, Map<String, Tag> schematic) {
BlockVector3 min = clipboard.getMinimumPoint();
int width = clipboard.getRegion().getWidth();
int length = clipboard.getRegion().getLength();
ByteArrayOutputStream buffer = new ByteArrayOutputStream(width * length);
int paletteMax = 0;
Map<String, Integer> palette = new HashMap<>();
for (int z = 0; z < length; z++) {
int z0 = min.getBlockZ() + z;
for (int x = 0; x < width; x++) {
int x0 = min.getBlockX() + x;
BlockVector2 pt = BlockVector2.at(x0, z0);
BiomeType biome = clipboard.getBiome(pt);
String biomeKey = biome.getId();
int biomeId;
if (palette.containsKey(biomeKey)) {
biomeId = palette.get(biomeKey);
} else {
biomeId = paletteMax;
palette.put(biomeKey, biomeId);
paletteMax++;
}
while ((biomeId & -128) != 0) {
buffer.write(biomeId & 127 | 128);
biomeId >>>= 7;
}
buffer.write(biomeId);
}
}
schematic.put("BiomePaletteMax", new IntTag(paletteMax));
Map<String, Tag> paletteTag = new HashMap<>();
palette.forEach((key, value) -> paletteTag.put(key, new IntTag(value)));
schematic.put("BiomePalette", new CompoundTag(paletteTag));
schematic.put("BiomeData", new ByteArrayTag(buffer.toByteArray()));
}
private void writeEntities(Clipboard clipboard, Map<String, Tag> schematic) {
List<CompoundTag> entities = clipboard.getEntities().stream().map(e -> {
BaseEntity state = e.getState();
if (state == null) {
return null;
}
Map<String, Tag> values = Maps.newHashMap();
CompoundTag rawData = state.getNbtData();
if (rawData != null) {
values.putAll(rawData.getValue());
}
values.remove("id");
values.put("Id", new StringTag(state.getType().getId()));
values.put("Pos", writeVector(e.getLocation().toVector()));
values.put("Rotation", writeRotation(e.getLocation()));
return new CompoundTag(values);
}).filter(e -> e != null).collect(Collectors.toList());
if (entities.isEmpty()) {
return;
}
schematic.put("Entities", new ListTag(CompoundTag.class, entities));
}
private Tag writeVector(Vector3 vector) {
List<DoubleTag> list = new ArrayList<DoubleTag>();
list.add(new DoubleTag(vector.getX()));
list.add(new DoubleTag(vector.getY()));
list.add(new DoubleTag(vector.getZ()));
return new ListTag(DoubleTag.class, list);
}
private Tag writeRotation(Location location) {
List<FloatTag> list = new ArrayList<FloatTag>();
list.add(new FloatTag(location.getYaw()));
list.add(new FloatTag(location.getPitch()));
return new ListTag(FloatTag.class, list);
}
@Override
public void close() throws IOException {
outputStream.close();

View File

@ -0,0 +1,32 @@
/*
* 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.sk89q.worldedit.extent.clipboard.io.legacycompat;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.world.block.BlockStateHolder;
import com.sk89q.worldedit.world.entity.EntityType;
import java.util.Map;
public interface EntityNBTCompatibilityHandler {
boolean isAffectedEntity(EntityType type, CompoundTag entityTag);
CompoundTag updateNBT(EntityType type, CompoundTag entityTag);
}

View File

@ -0,0 +1,93 @@
/*
* 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.sk89q.worldedit.extent.clipboard.io.legacycompat;
import com.sk89q.jnbt.IntTag;
import com.sk89q.jnbt.StringTag;
import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockStateHolder;
import com.sk89q.worldedit.world.block.BlockType;
import com.sk89q.worldedit.world.block.BlockTypes;
import com.sk89q.worldedit.world.registry.LegacyMapper;
import java.util.Map;
public class FlowerPotCompatibilityHandler implements NBTCompatibilityHandler {
@Override
public <B extends BlockStateHolder<B>> boolean isAffectedBlock(B block) {
return block.getBlockType() == BlockTypes.FLOWER_POT;
}
@Override
public <B extends BlockStateHolder<B>> B updateNBT(B block, Map<String, Tag> values) {
Tag item = values.get("Item");
if (item instanceof StringTag) {
String id = ((StringTag) item).getValue();
int data = 0;
Tag dataTag = values.get("Data");
if (dataTag instanceof IntTag) {
data = ((IntTag) dataTag).getValue();
}
BlockState newState = convertLegacyBlockType(id, data);
if (newState != null) {
return (B) newState; // generics pls :\
}
}
return block;
}
private BlockState convertLegacyBlockType(String id, int data) {
int newId = 0;
switch (id) {
case "minecraft:red_flower":
newId = 38; // now poppy
break;
case "minecraft:yellow_flower":
newId = 37; // now dandelion
break;
case "minecraft:sapling":
newId = 6; // oak_sapling
break;
case "minecraft:deadbush":
case "minecraft:tallgrass":
newId = 31; // dead_bush with fern and grass (not 32!)
break;
default:
break;
}
String plantedName = null;
if (newId == 0) {
plantedName = id.substring(10);
} else {
BlockState plantedWithData = LegacyMapper.getInstance().getBlockFromLegacy(newId, data);
if (plantedWithData != null) {
plantedName = plantedWithData.getBlockType().getId().substring(10); // remove "minecraft:"
}
}
if (plantedName != null) {
BlockType potAndPlanted = BlockTypes.get("minecraft:potted_" + plantedName);
if (potAndPlanted != null) {
return potAndPlanted.getDefaultState();
}
}
return null;
}
}

View File

@ -26,5 +26,5 @@ import java.util.Map;
public interface NBTCompatibilityHandler {
<B extends BlockStateHolder<B>> boolean isAffectedBlock(B block);
<B extends BlockStateHolder<B>> void updateNBT(B block, Map<String, Tag> values);
<B extends BlockStateHolder<B>> B updateNBT(B block, Map<String, Tag> values);
}

View File

@ -0,0 +1,62 @@
/*
* 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.sk89q.worldedit.extent.clipboard.io.legacycompat;
import com.sk89q.jnbt.ByteTag;
import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.registry.state.IntegerProperty;
import com.sk89q.worldedit.registry.state.Property;
import com.sk89q.worldedit.world.block.BlockStateHolder;
import com.sk89q.worldedit.world.block.BlockTypes;
import java.util.Map;
public class NoteBlockCompatibilityHandler implements NBTCompatibilityHandler {
private static final IntegerProperty NoteProperty;
static {
IntegerProperty temp;
try {
temp = (IntegerProperty) (Property<?>) BlockTypes.NOTE_BLOCK.getProperty("note");
} catch (NullPointerException | IllegalArgumentException | ClassCastException e) {
temp = null;
}
NoteProperty = temp;
}
@Override
public <B extends BlockStateHolder<B>> boolean isAffectedBlock(B block) {
return NoteProperty != null && block.getBlockType() == BlockTypes.NOTE_BLOCK;
}
@Override
public <B extends BlockStateHolder<B>> B updateNBT(B block, Map<String, Tag> values) {
// note that instrument was not stored (in state or nbt) previously.
// it will be updated to the block below when it gets set into the world for the first time
Tag noteTag = values.get("note");
if (noteTag instanceof ByteTag) {
Byte note = ((ByteTag) noteTag).getValue();
if (note != null) {
return block.with(NoteProperty, (int) note);
}
}
return block;
}
}

View File

@ -0,0 +1,62 @@
/*
* 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.sk89q.worldedit.extent.clipboard.io.legacycompat;
import com.sk89q.jnbt.ByteTag;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.CompoundTagBuilder;
import com.sk89q.worldedit.internal.helper.MCDirections;
import com.sk89q.worldedit.util.Direction;
import com.sk89q.worldedit.world.entity.EntityType;
public class Pre13HangingCompatibilityHandler implements EntityNBTCompatibilityHandler {
@Override
public boolean isAffectedEntity(EntityType type, CompoundTag tag) {
if (!type.getId().startsWith("minecraft:")) {
return false;
}
boolean hasLegacyDirection = tag.containsKey("Dir") || tag.containsKey("Direction");
boolean hasFacing = tag.containsKey("Facing");
return hasLegacyDirection || hasFacing;
}
@Override
public CompoundTag updateNBT(EntityType type, CompoundTag tag) {
boolean hasLegacyDir = tag.containsKey("Dir");
boolean hasLegacyDirection = tag.containsKey("Direction");
boolean hasPre113Facing = tag.containsKey("Facing");
Direction newDirection;
if (hasLegacyDir) {
newDirection = MCDirections.fromPre13Hanging(MCDirections.fromLegacyHanging((byte) tag.asInt("Dir")));
} else if (hasLegacyDirection) {
newDirection = MCDirections.fromPre13Hanging(tag.asInt("Direction"));
} else if (hasPre113Facing) {
newDirection = MCDirections.fromPre13Hanging(tag.asInt("Facing"));
} else {
return tag;
}
byte hangingByte = (byte) MCDirections.toHanging(newDirection);
CompoundTagBuilder builder = tag.createBuilder();
builder.putByte("Facing", hangingByte);
return builder.build();
}
}

View File

@ -39,7 +39,7 @@ public class SignCompatibilityHandler implements NBTCompatibilityHandler {
}
@Override
public <B extends BlockStateHolder<B>> void updateNBT(B block, Map<String, Tag> values) {
public <B extends BlockStateHolder<B>> B updateNBT(B block, Map<String, Tag> values) {
for (int i = 0; i < 4; ++i) {
String key = "Text" + (i + 1);
Tag value = values.get(key);
@ -69,5 +69,6 @@ public class SignCompatibilityHandler implements NBTCompatibilityHandler {
values.put("Text" + (i + 1), new StringTag(jsonTextObject.toString()));
}
}
return block;
}
}

View File

@ -0,0 +1,100 @@
/*
* 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.sk89q.worldedit.extent.clipboard.io.legacycompat;
import com.sk89q.jnbt.ByteTag;
import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.registry.state.DirectionalProperty;
import com.sk89q.worldedit.registry.state.Property;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockStateHolder;
import com.sk89q.worldedit.world.block.BlockType;
import com.sk89q.worldedit.world.block.BlockTypes;
import java.util.Map;
public class SkullBlockCompatibilityHandler implements NBTCompatibilityHandler {
private static final DirectionalProperty FacingProperty;
static {
DirectionalProperty tempFacing;
try {
tempFacing = (DirectionalProperty) (Property<?>) BlockTypes.SKELETON_WALL_SKULL.getProperty("facing");
} catch (NullPointerException | IllegalArgumentException | ClassCastException e) {
tempFacing = null;
}
FacingProperty = tempFacing;
}
@Override
public <B extends BlockStateHolder<B>> boolean isAffectedBlock(B block) {
return block.getBlockType() == BlockTypes.SKELETON_SKULL
|| block.getBlockType() == BlockTypes.SKELETON_WALL_SKULL;
}
@Override
public <B extends BlockStateHolder<B>> B updateNBT(B block, Map<String, Tag> values) {
boolean isWall = block.getBlockType() == BlockTypes.SKELETON_WALL_SKULL;
Tag typeTag = values.get("SkullType");
if (typeTag instanceof ByteTag) {
String skullType = convertSkullType(((ByteTag) typeTag).getValue(), isWall);
if (skullType != null) {
BlockType type = BlockTypes.get("minecraft:" + skullType);
if (type != null) {
BlockState state = type.getDefaultState();
if (isWall) {
Property newProp = type.getProperty("facing");
state = state.with(newProp, block.getState(FacingProperty));
} else {
Tag rotTag = values.get("Rot");
if (rotTag instanceof ByteTag) {
Property newProp = type.getProperty("rotation");
state = state.with(newProp, (int) ((ByteTag) rotTag).getValue());
}
}
values.remove("SkullType");
values.remove("Rot");
return (B) state;
}
}
}
return block;
}
private String convertSkullType(Byte oldType, boolean isWall) {
switch (oldType) {
case 0:
return isWall ? "skeleton_wall_skull" : "skeleton_skull";
case 1:
return isWall ? "wither_skeleton_wall_skull" : "wither_skeleton_skull";
case 2:
return isWall ? "zombie_wall_head" : "zombie_head";
case 3:
return isWall ? "player_wall_head" : "player_head";
case 4:
return isWall ? "creeper_wall_head" : "creeper_head";
case 5:
return isWall ? "dragon_wall_head" : "dragon_head";
default:
return null;
}
}
}

View File

@ -0,0 +1,72 @@
/*
* 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.sk89q.worldedit.function.biome;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.function.FlatRegionFunction;
import com.sk89q.worldedit.math.BlockVector2;
import com.sk89q.worldedit.math.transform.Transform;
import com.sk89q.worldedit.world.biome.BiomeType;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Copies the biome from one extent to another.
*/
public class ExtentBiomeCopy implements FlatRegionFunction {
private final Extent source;
private final Extent destination;
private final BlockVector2 from;
private final BlockVector2 to;
private final Transform transform;
/**
* Make a new biome copy.
*
* @param source the source extent
* @param from the source offset
* @param destination the destination extent
* @param to the destination offset
* @param transform a transform to apply to positions (after source offset, before destination offset)
*/
public ExtentBiomeCopy(Extent source, BlockVector2 from, Extent destination, BlockVector2 to, Transform transform) {
checkNotNull(source);
checkNotNull(from);
checkNotNull(destination);
checkNotNull(to);
checkNotNull(transform);
this.source = source;
this.from = from;
this.destination = destination;
this.to = to;
this.transform = transform;
}
@Override
public boolean apply(BlockVector2 position) throws WorldEditException {
BiomeType biome = source.getBiome(position);
BlockVector2 orig = position.subtract(from);
BlockVector2 transformed = transform.apply(orig.toVector3(0)).toVector2().toBlockPoint();
return destination.setBiome(transformed.add(to), biome);
}
}

View File

@ -130,8 +130,6 @@ public class ExtentEntityCopy implements EntityFunction {
if (tag != null) {
// Handle hanging entities (paintings, item frames, etc.)
boolean hasTilePosition = tag.containsKey("TileX") && tag.containsKey("TileY") && tag.containsKey("TileZ");
boolean hasDirection = tag.containsKey("Direction");
boolean hasLegacyDirection = tag.containsKey("Dir");
boolean hasFacing = tag.containsKey("Facing");
if (hasTilePosition) {
@ -143,27 +141,15 @@ public class ExtentEntityCopy implements EntityFunction {
.putInt("TileY", newTilePosition.getBlockY())
.putInt("TileZ", newTilePosition.getBlockZ());
if (hasDirection || hasLegacyDirection || hasFacing) {
int d;
if (hasDirection) {
d = tag.asInt("Direction");
} else if (hasLegacyDirection) {
d = MCDirections.fromLegacyHanging((byte) tag.asInt("Dir"));
} else {
d = tag.asInt("Facing");
}
Direction direction = MCDirections.fromHanging(d);
if (hasFacing) {
Direction direction = MCDirections.fromHanging(tag.asInt("Facing"));
if (direction != null) {
Vector3 vector = transform.apply(direction.toVector()).subtract(transform.apply(Vector3.ZERO)).normalize();
Direction newDirection = Direction.findClosest(vector, Flag.CARDINAL);
if (newDirection != null) {
byte hangingByte = (byte) MCDirections.toHanging(newDirection);
builder.putByte("Direction", hangingByte);
builder.putByte("Facing", hangingByte);
builder.putByte("Dir", MCDirections.toLegacyHanging(MCDirections.toHanging(newDirection)));
builder.putByte("Facing", (byte) MCDirections.toHanging(newDirection));
}
}
}

View File

@ -28,17 +28,23 @@ import com.sk89q.worldedit.entity.Entity;
import com.sk89q.worldedit.entity.metadata.EntityProperties;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.function.CombinedRegionFunction;
import com.sk89q.worldedit.function.FlatRegionFunction;
import com.sk89q.worldedit.function.FlatRegionMaskingFilter;
import com.sk89q.worldedit.function.RegionFunction;
import com.sk89q.worldedit.function.RegionMaskingFilter;
import com.sk89q.worldedit.function.biome.ExtentBiomeCopy;
import com.sk89q.worldedit.function.block.ExtentBlockCopy;
import com.sk89q.worldedit.function.entity.ExtentEntityCopy;
import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.function.mask.Mask2D;
import com.sk89q.worldedit.function.mask.Masks;
import com.sk89q.worldedit.function.visitor.EntityVisitor;
import com.sk89q.worldedit.function.visitor.FlatRegionVisitor;
import com.sk89q.worldedit.function.visitor.RegionVisitor;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.math.transform.Identity;
import com.sk89q.worldedit.math.transform.Transform;
import com.sk89q.worldedit.regions.FlatRegion;
import com.sk89q.worldedit.regions.Region;
import java.util.List;
@ -61,11 +67,18 @@ public class ForwardExtentCopy implements Operation {
private Mask sourceMask = Masks.alwaysTrue();
private boolean removingEntities;
private boolean copyingEntities = true; // default to true for backwards compatibility, sort of
private boolean copyingBiomes;
private RegionFunction sourceFunction = null;
private Transform transform = new Identity();
private Transform currentTransform = null;
private RegionVisitor lastVisitor;
private int affected;
private FlatRegionVisitor lastBiomeVisitor;
private EntityVisitor lastEntityVisitor;
private int affectedBlocks;
private int affectedBiomeCols;
private int affectedEntities;
/**
* Create a new copy using the region's lowest minimum point as the
@ -222,21 +235,50 @@ public class ForwardExtentCopy implements Operation {
this.removingEntities = removingEntities;
}
/**
* Return whether biomes should be copied along with blocks.
*
* @return true if copying biomes
*/
public boolean isCopyingBiomes() {
return copyingBiomes;
}
/**
* Set whether biomes should be copies along with blocks.
*
* @param copyingBiomes true if copying
*/
public void setCopyingBiomes(boolean copyingBiomes) {
if (copyingBiomes && !(region instanceof FlatRegion)) {
throw new UnsupportedOperationException("Can't copy biomes from region that doesn't implement FlatRegion");
}
this.copyingBiomes = copyingBiomes;
}
/**
* Get the number of affected objects.
*
* @return the number of affected
*/
public int getAffected() {
return affected;
return affectedBlocks + affectedBiomeCols + affectedEntities;
}
@Override
public Operation resume(RunContext run) throws WorldEditException {
if (lastVisitor != null) {
affected += lastVisitor.getAffected();
affectedBlocks += lastVisitor.getAffected();
lastVisitor = null;
}
if (lastBiomeVisitor != null) {
affectedBiomeCols += lastBiomeVisitor.getAffected();
lastBiomeVisitor = null;
}
if (lastEntityVisitor != null) {
affectedEntities += lastEntityVisitor.getAffected();
lastEntityVisitor = null;
}
if (repetitions > 0) {
repetitions--;
@ -254,6 +296,23 @@ public class ForwardExtentCopy implements Operation {
lastVisitor = blockVisitor;
if (!copyingBiomes && !copyingEntities) {
return new DelegateOperation(this, blockVisitor);
}
List<Operation> ops = Lists.newArrayList(blockVisitor);
if (copyingBiomes && region instanceof FlatRegion) { // double-check here even though we checked before
ExtentBiomeCopy biomeCopy = new ExtentBiomeCopy(source, from.toBlockVector2(),
destination, to.toBlockVector2(), currentTransform);
Mask2D biomeMask = sourceMask.toMask2D();
FlatRegionFunction biomeFunction = biomeMask == null ? biomeCopy
: new FlatRegionMaskingFilter(biomeMask, biomeCopy);
FlatRegionVisitor biomeVisitor = new FlatRegionVisitor(((FlatRegion) region), biomeFunction);
ops.add(biomeVisitor);
lastBiomeVisitor = biomeVisitor;
}
if (copyingEntities) {
ExtentEntityCopy entityCopy = new ExtentEntityCopy(from.toVector3(), destination, to.toVector3(), currentTransform);
entityCopy.setRemoving(removingEntities);
@ -263,10 +322,11 @@ public class ForwardExtentCopy implements Operation {
return properties != null && !properties.isPasteable();
});
EntityVisitor entityVisitor = new EntityVisitor(entities.iterator(), entityCopy);
return new DelegateOperation(this, new OperationQueue(blockVisitor, entityVisitor));
} else {
return new DelegateOperation(this, blockVisitor);
ops.add(entityVisitor);
lastEntityVisitor = entityVisitor;
}
return new DelegateOperation(this, new OperationQueue(ops));
} else {
return null;
}
@ -278,6 +338,24 @@ public class ForwardExtentCopy implements Operation {
@Override
public void addStatusMessages(List<String> messages) {
StringBuilder msg = new StringBuilder();
msg.append(affectedBlocks).append(" block(s)");
if (affectedBiomeCols > 0) {
if (affectedEntities > 0) {
msg.append(", ");
} else {
msg.append(" and ");
}
msg.append(affectedBiomeCols).append(" biome(s)");
}
if (affectedEntities > 0) {
if (affectedBiomeCols > 0) {
msg.append(",");
}
msg.append(" and ").append(affectedEntities).append(" entities(s)");
}
msg.append(" affected.");
messages.add(msg.toString());
}
}

View File

@ -30,6 +30,44 @@ public final class MCDirections {
}
public static Direction fromHanging(int i) {
switch (i) {
case 0:
return Direction.DOWN;
case 1:
return Direction.UP;
case 2:
return Direction.NORTH;
case 3:
return Direction.SOUTH;
case 4:
return Direction.WEST;
case 5:
return Direction.EAST;
default:
return Direction.DOWN;
}
}
public static int toHanging(Direction direction) {
switch (direction) {
case DOWN:
return 0;
case UP:
return 1;
case NORTH:
return 2;
case SOUTH:
return 3;
case WEST:
return 4;
case EAST:
return 5;
default:
return 0;
}
}
public static Direction fromPre13Hanging(int i) {
switch (i) {
case 0:
return Direction.SOUTH;
@ -44,21 +82,6 @@ public final class MCDirections {
}
}
public static int toHanging(Direction direction) {
switch (direction) {
case SOUTH:
return 0;
case WEST:
return 1;
case NORTH:
return 2;
case EAST:
return 3;
default:
return 0;
}
}
public static int fromLegacyHanging(byte i) {
switch (i) {
case 0: return 2;
@ -68,15 +91,6 @@ public final class MCDirections {
}
}
public static byte toLegacyHanging(int i) {
switch (i) {
case 0: return (byte) 2;
case 1: return (byte) 1;
case 2: return (byte) 0;
default: return (byte) 3;
}
}
public static Direction fromRotation(int i) {
switch (i) {
case 0:

View File

@ -25,6 +25,9 @@ import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.extent.transform.BlockTransformExtent;
import com.sk89q.worldedit.function.mask.ExistingBlockMask;
import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.function.mask.MaskIntersection;
import com.sk89q.worldedit.function.mask.Masks;
import com.sk89q.worldedit.function.operation.ForwardExtentCopy;
import com.sk89q.worldedit.function.operation.Operation;
import com.sk89q.worldedit.math.BlockVector3;
@ -39,8 +42,12 @@ public class PasteBuilder {
private final Transform transform;
private final Extent targetExtent;
private Mask sourceMask = Masks.alwaysTrue();
private BlockVector3 to = BlockVector3.ZERO;
private boolean ignoreAirBlocks;
private boolean copyEntities = true; // default because it used to be this way
private boolean copyBiomes;
/**
* Create a new instance.
@ -67,6 +74,23 @@ public class PasteBuilder {
return this;
}
/**
* Set a custom mask of blocks to ignore from the source.
* This provides a more flexible alternative to {@link #ignoreAirBlocks(boolean)}, for example
* one might want to ignore structure void if copying a Minecraft Structure, etc.
*
* @param sourceMask
* @return this builder instance
*/
public PasteBuilder maskSource(Mask sourceMask) {
if (sourceMask == null) {
this.sourceMask = Masks.alwaysTrue();
return this;
}
this.sourceMask = sourceMask;
return this;
}
/**
* Set whether air blocks in the source are skipped over when pasting.
*
@ -77,6 +101,29 @@ public class PasteBuilder {
return this;
}
/**
* Set whether the copy should include source entities.
* Note that this is true by default for legacy reasons.
*
* @param copyEntities
* @return this builder instance
*/
public PasteBuilder copyEntities(boolean copyEntities) {
this.copyEntities = copyEntities;
return this;
}
/**
* Set whether the copy should include source biomes (if available).
*
* @param copyBiomes
* @return this builder instance
*/
public PasteBuilder copyBiomes(boolean copyBiomes) {
this.copyBiomes = copyBiomes;
return this;
}
/**
* Build the operation.
*
@ -87,8 +134,13 @@ public class PasteBuilder {
ForwardExtentCopy copy = new ForwardExtentCopy(extent, clipboard.getRegion(), clipboard.getOrigin(), targetExtent, to);
copy.setTransform(transform);
if (ignoreAirBlocks) {
copy.setSourceMask(new ExistingBlockMask(clipboard));
copy.setSourceMask(sourceMask == Masks.alwaysTrue() ? new ExistingBlockMask(clipboard)
: new MaskIntersection(sourceMask, new ExistingBlockMask(clipboard)));
} else {
copy.setSourceMask(sourceMask);
}
copy.setCopyingEntities(copyEntities);
copy.setCopyingBiomes(copyBiomes && clipboard.hasBiomes());
return copy;
}

View File

@ -52,7 +52,7 @@ public final class NBTConversions {
return new Location(
extent,
positionTag.asDouble(0), positionTag.asDouble(1), positionTag.asDouble(2),
(float) directionTag.asDouble(0), (float) directionTag.asDouble(1));
directionTag.getFloat(0), directionTag.getFloat(1));
}
}

View File

@ -69,6 +69,12 @@ class ForgePlatform extends AbstractPlatform implements MultiUserPlatform {
return ForgeRegistries.getInstance();
}
@Override
public int getDataVersion() {
// TODO switch to SharedConstants in 1.14
return 1631;
}
@Override
public boolean isValidMobType(String type) {
return net.minecraftforge.registries.ForgeRegistries.ENTITIES.containsKey(new ResourceLocation(type));

View File

@ -73,6 +73,12 @@ class SpongePlatform extends AbstractPlatform implements MultiUserPlatform {
return SpongeRegistries.getInstance();
}
@Override
public int getDataVersion() {
// TODO add to adapter - org.spongepowered.common.data.util.DataUtil#MINECRAFT_DATA_VERSION
return 1631;
}
@Override
public boolean isValidMobType(String type) {
return Sponge.getRegistry().getType(EntityType.class, type).isPresent();