/*
* This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT).
*
* Copyright (c) JCThePants (www.jcwhatever.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.jcwhatever.nucleus.utils.file;
import com.jcwhatever.nucleus.Nucleus;
import com.jcwhatever.nucleus.managed.scheduler.Scheduler;
import com.jcwhatever.nucleus.utils.PreCon;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Note;
import org.bukkit.SkullType;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState;
import org.bukkit.block.CommandBlock;
import org.bukkit.block.CreatureSpawner;
import org.bukkit.block.NoteBlock;
import org.bukkit.block.Sign;
import org.bukkit.block.Skull;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import java.io.IOException;
import javax.annotation.Nullable;
/**
* A generic serializable wrapper for a BlockState that is
* specifically designed to store tile entity data.
*
* <p>Cannot create a {@link org.bukkit.block.BlockState} instance but instead
* retains the information necessary to apply the data to the original block location.</p>
*
* <p>Supported Tile Entities:</p>
*
* <ul>
* <li>{@link org.bukkit.inventory.InventoryHolder}</li>
* <li>{@link org.bukkit.block.CommandBlock}</li>
* <li>{@link org.bukkit.block.CreatureSpawner}</li>
* <li>{@link org.bukkit.block.NoteBlock}</li>
* <li>{@link org.bukkit.block.Sign}</li>
* <li>{@link org.bukkit.block.Skull}</li>
* </ul>
*/
public class SerializableBlockEntity implements IAppliedSerializable {
private Location _location;
private Material _material;
private byte _data;
// InventoryHolder
private ItemStack[] _contents;
// command blocks
private String _commandName;
private String _command;
// CreatureSpawner
private String _creatureTypeName;
private int _creatureDelay;
// NoteBlock
private Note.Tone _noteTone;
private int _noteOctave;
private boolean _noteSharped;
// Sign
private String[] _signLines;
// Skull
private SkullType _skullType;
private BlockFace _skullRotation;
private String _skullOwner; // nullable
/**
* Constructor.
*
* <p>Required by {@link BasicByteReader} to deserialize.</p>
*/
private SerializableBlockEntity() {}
/**
* Constructor.
*
* @param blockState The {@link org.bukkit.block.BlockState} that needs to be serialized.
*/
public SerializableBlockEntity(BlockState blockState) {
PreCon.notNull(blockState);
_location = blockState.getLocation();
_material = blockState.getType();
_data = blockState.getRawData();
if (blockState instanceof InventoryHolder) {
_contents = ((InventoryHolder) blockState).getInventory().getContents();
}
if (blockState instanceof CommandBlock) {
CommandBlock commandBlock = (CommandBlock)blockState;
_commandName = commandBlock.getName();
_command = commandBlock.getCommand();
}
if (blockState instanceof CreatureSpawner) {
CreatureSpawner spawner = (CreatureSpawner)blockState;
_creatureTypeName = spawner.getCreatureTypeName();
_creatureDelay = spawner.getDelay();
}
if (blockState instanceof NoteBlock) {
NoteBlock noteBlock = (NoteBlock)blockState;
_noteTone = noteBlock.getNote().getTone();
_noteOctave = noteBlock.getNote().getOctave();
_noteSharped = noteBlock.getNote().isSharped();
}
if (blockState instanceof Sign) {
Sign sign = (Sign)blockState;
_signLines = sign.getLines().clone();
}
if (blockState instanceof Skull) {
Skull skull = (Skull)blockState;
_skullType = skull.getSkullType();
_skullRotation = skull.getRotation();
_skullOwner = skull.getOwner();
}
}
/**
* Get the location of the block
* the serialized {@link org.bukkit.block.BlockState} represents.
*/
@Nullable
public Location getLocation() {
return _location;
}
/**
* Get the {@link Material} of the block.
*/
@Nullable
public Material getMaterial() {
return _material;
}
/**
* Get the raw byte data.
*/
public byte getRawData() {
return _data;
}
/**
* Apply the stored {@link org.bukkit.block.BlockState} data to the location
* the original {@link org.bukkit.block.BlockState} was taken from.
*/
@Override
public boolean apply() {
if (getLocation() == null)
return false;
final BlockState blockState = getLocation().getBlock().getState();
if (blockState.getType() == _material) {
if (blockState.getRawData() != getRawData())
_location.getBlock().setData(getRawData());
applyTile(blockState);
}
else {
_location.getBlock().setType(getMaterial());
_location.getBlock().setData(getRawData());
Scheduler.runTaskLater(Nucleus.getPlugin(), new Runnable() {
@Override
public void run() {
applyTile(blockState);
}
});
}
return true;
}
@Override
public void serialize(IByteWriter writer) throws IOException {
if (getLocation() == null || getMaterial() == null)
throw new RuntimeException("No data to serialize.");
writer.write(getLocation());
writer.write(getMaterial());
writer.write(getRawData());
boolean addInventoryHolder = _contents != null;
boolean addCommandBlock = _commandName != null;
boolean addCreatureSpawner = _creatureTypeName != null;
boolean addNoteBlock = _noteTone != null;
boolean addSign = _signLines != null;
boolean addSkull = _skullType != null;
writer.write(addInventoryHolder);
writer.write(addCommandBlock);
writer.write(addCreatureSpawner);
writer.write(addNoteBlock);
writer.write(addSign);
writer.write(addSkull);
// InventoryHolder
if (addInventoryHolder) {
writer.write(_contents.length);
for (ItemStack content : _contents) {
writer.write(content);
}
}
// CommandBlocks
if (addCommandBlock) {
writer.write(_commandName);
writer.write(_command);
}
// CreatureSpawner
if (addCreatureSpawner) {
writer.write(_creatureTypeName);
writer.write(_creatureDelay);
}
// NoteBlock
if (addNoteBlock) {
writer.write(_noteTone);
writer.write(_noteOctave);
writer.write(_noteSharped);
}
// Sign
if (addSign) {
for (int i=0; i < 4; i++) {
writer.write(_signLines[i]);
}
}
// Skull
if (addSkull) {
writer.write(_skullType);
writer.write(_skullRotation);
writer.write(_skullOwner);
}
}
@Override
public void deserialize(IByteReader reader) throws IOException {
_location = reader.getLocation();
_material = reader.getEnum(Material.class);
_data = reader.getByte();
boolean hasInventoryHolder = reader.getBoolean();
boolean hasCommandBlock = reader.getBoolean();
boolean hasCreatureSpawner = reader.getBoolean();
boolean hasNoteBlock = reader.getBoolean();
boolean addSign = reader.getBoolean();
boolean addSkull = reader.getBoolean();
// InventoryHolder
if (hasInventoryHolder) {
// iterate content items
int size = reader.getInteger();
_contents = new ItemStack[size];
for (int i=0; i < _contents.length; i++) {
_contents[i] = reader.getItemStack();
}
}
// CommandBlocks
if (hasCommandBlock) {
_commandName = reader.getString();
_command = reader.getString();
}
// CreatureSpawner
if (hasCreatureSpawner) {
_creatureTypeName = reader.getString();
_creatureDelay = reader.getInteger();
}
// NoteBlock
if (hasNoteBlock) {
_noteTone = reader.getEnum(Note.Tone.class);
_noteOctave = reader.getInteger();
_noteSharped = reader.getBoolean();
}
// Sign
if (addSign) {
_signLines = new String[4];
for (int i=0; i < 4; i++) {
_signLines[i] = reader.getString();
}
}
// Skull
if (addSkull) {
_skullType = reader.getEnum(SkullType.class);
_skullRotation = reader.getEnum(BlockFace.class);
_skullOwner = reader.getString();
}
}
/*
* Apply stored tile entity data to the supplied BlockState.
*/
private void applyTile(BlockState blockState) {
boolean requiresUpdate = false;
// InventoryHolder
if (blockState instanceof InventoryHolder && _contents != null) {
InventoryHolder holder = (InventoryHolder)blockState;
Inventory inventory = holder.getInventory();
inventory.setContents(_contents);
requiresUpdate = true;
}
// CommandBlock
if (blockState instanceof CommandBlock) {
CommandBlock commandBlock = (CommandBlock)blockState;
if (_commandName != null)
commandBlock.setName(_commandName);
if (_command != null)
commandBlock.setCommand(_command);
requiresUpdate = true;
}
// CreatureSpawner
if (blockState instanceof CreatureSpawner) {
CreatureSpawner spawner = (CreatureSpawner)blockState;
if (_creatureTypeName != null) {
spawner.setCreatureTypeByName(_creatureTypeName);
spawner.setDelay(_creatureDelay);
}
requiresUpdate = true;
}
if (blockState instanceof NoteBlock && _noteTone != null) {
NoteBlock noteBlock = (NoteBlock)blockState;
Note note = new Note(_noteOctave, _noteTone, _noteSharped);
noteBlock.setNote(note);
requiresUpdate = true;
}
if (blockState instanceof Sign && _signLines != null) {
Sign sign = (Sign)blockState;
for (int i=0; i < 4; i++)
sign.setLine(i, _signLines[i]);
requiresUpdate = true;
}
if (blockState instanceof Skull && _skullType != null) {
Skull skull = (Skull)blockState;
skull.setSkullType(_skullType);
skull.setRotation(_skullRotation);
skull.setOwner(_skullOwner);
requiresUpdate = true;
}
if (requiresUpdate) {
blockState.update(true);
}
}
}