15方块API
监听器:
public class BlockListener implements Listener {
@EventHandler
public void onStickRightClick(PlayerInteractEvent event) {
// 当玩家进行互动时,这个方法会被触发两次,因为是两只手都参与到了互动(
if (event.getHand() == EquipmentSlot.HAND) {
Block clickedBlock = event.getClickedBlock();
ItemStack itemInMainHand = event.getPlayer().getInventory().getItemInMainHand();
// 判断手里有没有物品
if (Objects.isNull(itemInMainHand))
return;
// 根据手上木棍的名称来演示不同的案例
if (itemInMainHand.getType() == Material.STICK) {
// 和物品栏里的空物品一样,点击空气所返还的空气方块也是null值
if (Objects.nonNull(clickedBlock)) {
String displayName = itemInMainHand.getItemMeta().getDisplayName();
switch (displayName) {
case "1":
// Block
new BlockExample().doBlockAPI(clickedBlock, event.getPlayer());
break;
case "2":
// 获取并修改BlockData
new BlockDataExample().doGetAndSetBlockData(clickedBlock, event.getPlayer());
break;
case "3":
// 方块更新
new ApplyPhysicsExample().doSetBlockData(clickedBlock, event.getPlayer());
break;
case "4":
// BlockState为何物
new BlockStateExample().doGetBlockState(clickedBlock, event.getPlayer());
break;
}
event.getPlayer().sendMessage("boom");
}
}
}
}
}
四种逻辑:
/**
* 有关Block方块的示例
*/
public class BlockExample {
/**
* {@link Block}的存在就跟{@link org.bukkit.inventory.ItemStack}类似,它不保存方块的内部信息,
* 真正的方块内部信息分如箱子内容、告示牌内容则由{@link Block#getState()}所得到的对象存储。
* 正因为有这么多复杂多变的属性,我们不可能通过简单的方法去new出一个方块,
* 因此正常的做法是通过 事件系统或{@link org.bukkit.World#getBlockAt(Location)} 等方式
* 来“获取” 方块,而非 ”new“ 出方块。
* <p>
* Block方块主要代表了在当前世界。当前坐标下这个方块的状态信息,
* 比如方块是什么、光照等级是多少、生态群系是什么、它的坐标是什么,它所在的世界是什么等等
* <p>
* {@link org.bukkit.block.BlockState}里的内容看着好像和{@link Block}没什么不同,
* 但它的作用并不是表面的这些,而是转型成相应的子类,得到特殊方块的内部信息。比如它的子类箱子{@link org.bukkit.block.Chest}
* 命令方块{@link org.bukkit.block.CommandBlock}。
*/
public void doBlockAPI(Block block, Player player) {
Material type = block.getType();
player.sendMessage("方块类型:" + type.toString());
Block relativeEast = block.getRelative(BlockFace.EAST);
player.sendMessage("与方块相邻的东边的方块:" + relativeEast.getType().toString());
Location location = block.getLocation();
player.sendMessage("方块的坐标:" + location.toString());
Biome biome = block.getBiome();
player.sendMessage("方块所在的生态群系:" + biome.toString());
int blockPower = block.getBlockPower();
player.sendMessage("方块的红石等级:" + blockPower);
int blockLightLevel = block.getLightLevel();
player.sendMessage("方块的光照等级:" + blockLightLevel);
// block.getChunk(); // 获取方块所在的Chunk,Chunk API有关,本期视频不细讲
byte lightLevel = block.getLightLevel();
player.sendMessage("方块的光照等级:" + lightLevel);
//...还有更多API,自己发掘吧
// 修改方块的生态群戏
block.setBiome(Biome.BADLANDS);
// 生成掉落中的方块
Location blockLocation = block.getLocation();
Location clonedLocation = blockLocation.clone(); // 获取Location位置的副本,防止修改location会同步影响到原方块
clonedLocation.add(0, 10, 0); // y轴增加10格
block.getWorld().spawnFallingBlock(clonedLocation, Bukkit.createBlockData(Material.TNT));
/**
* 真正负责存储方块内部数据的两个类
*/
// block.getState();
// block.getBlockData();
}
}
有关BlockData的基本使用
/**
* 有关BlockData的基本使用(获取方块信息,以及序列化和反序列化)
*/
public class BlockDataExample {
/**
* BlockData在游戏中的体现就是F3键按下后,在屏幕中心右侧显示的Target Block下的那些内容
* (#开头的是注解,因此不会被包含在BlockData里)
* <p>
* 对于存储离散不连续的多个方块的需求,一般都是记录Location,BlockData#getAsString()序列化BlockData,然后通过某种方式存储起来。
* 等需要的时候,再Bukkit#createBlockData(String)反序列化回BlockData,然后用{@link org.bukkit.World#setBlockData(Location, BlockData)}把方块设置在某处。
* (但是BlockData并不是方块的全部,还有BlockState会记负责录方块的详细数据,比如箱子存储了什么、命令方块里的命令是什么等等,
* 所以这种方法只能设置什么位置是什么方块,并不能连同详细数据一同序列化。
* <p>
* 而且Spigot/Bukkit提供的这些修改BlockData的API都很笨重,如果要修改大批量的方块,
* 将会严重增大服务器的性能消耗,除非你抛开Spigot/Bukkit繁重的代码,直接利用底层代码NMS来修改,
* 又或者是使用WorldEdit API(推荐)。
*/
public void doGetAndSetBlockData(Block block, Player player) {
// TIP: 获取到的BlockData和Location一样,都是对象属性的内存地址,如果要进行修改,请尽量先clone()出副本再针对副本进行修改
BlockData blockData = block.getBlockData();
// 比如这里你点击了一个箱子
if (block.getType() == Material.CHEST) {
Chest chestData = ((Chest) blockData); // 是箱子,就转换成BlockData的子类Chest
player.sendMessage("点击箱子的方向是:" + chestData.getFacing().toString()); // 箱子BlockData所特有的信息
player.sendMessage("点击箱子是否含水:" + (chestData.isWaterlogged() ? "是" : "否"));
player.sendMessage("点击箱子的类型:" + chestData.getType().name());
player.sendMessage("点击箱子的完整data:" + blockData.getAsString()); // blockData的String形式
// chestData....
// BlockData blockData1 = Bukkit.createBlockData(blockData.getAsString()); // 还有其他方式可以用来(获取)BlockData
// BlockData blockData1 = Bukkit.createBlockData(Material.CHEST);
changeAnotherBlock(block, chestData); // 利用BlockData去修改其他的方块
} else {
player.sendMessage("你点击的方块不是箱子,没有方向信息");
}
}
private void changeAnotherBlock(Block curBlock, Chest chestData) {
Block relative = curBlock.getRelative(chestData.getFacing()); // 获取chest对面的方块
Chest randomChestData = getRandomChestData(chestData); // 随机修改chest的data。
relative.setBlockData(randomChestData); // 修改相对方块为随机掉data的箱子
}
private Chest getRandomChestData(Chest chestData) {
// 这里先克隆再修改,是为了不在修改时影响到原方块
Chest clonedChestData = (Chest) chestData.clone();
Random random = new Random();
int nextType = random.nextInt(Chest.Type.values().length);
clonedChestData.setType(Chest.Type.values()[nextType]); // 通过API修改方块Type
clonedChestData.setWaterlogged(!clonedChestData.isWaterlogged()); // 修改方块是否含水
return clonedChestData; // 返回新的BlockData
}
}
ApplyPhysicsExample
public class ApplyPhysicsExample {
public void doSetBlockData(Block clickedBlock, Player player) {
// 如果是箱子就将箱子删除
if (clickedBlock.getType() == Material.CHEST){
BlockData airBlockData = Bukkit.createBlockData(Material.AIR);
player.sendMessage("现在我要删除该箱子,且不会引发周围的方块更新!");
/**
* 第二个参数applyPhysics = false,意味着修改方块信息不会引发周围的方块更新。
* 因为有些情况,插件需要大面积的修改方块,这时候如果选择触发方块更新,可能会出现意想不到的效果
* (腐竹,你也不想让服务器因为巨浪的方块更新计算导致暴毙吧)。
* 比如著名的/co i指令,如果管理员选择对玩家进行事务回滚操作。最后回滚完的区域可能留下了
* 浮空火把,又或者是we里对地形的改造。都选择了不触发方块更新。
*
* {@link Block#setType(Material, boolean)} 也可以修改方块的类型,也可以设置是否触发方块更新。
* 但是原有的方块状态BlockState和方块信息BlockData会被覆盖为默认的,和这里的Bukkit.createBlockData(Material.AIR)有点类似。
*/
clickedBlock.setBlockData(airBlockData, true); // 关闭方块更新 的修改
// clickedBlock.setBlockData(airBlockData); // 默认启用方块更新 的修改
}
}
}
BlockStateExample
/**
* 方块状态的示例
*/
public class BlockStateExample {
public void doGetBlockState(Block block, Player player) {
// 如果被点击的方块是命令方块
if (block.getType() == Material.COMMAND_BLOCK) {
/**
* BlockState是个基类,每个Block都具有,
* 但是对于一些特殊的,可以存放信息的方块,比如命令方块,告示牌,箱子等,
* 则有相应的BlockState的子类,比如{@link CommandBlock},{@link org.bukkit.block.Sign},{@link org.bukkit.block.Chest}等,
* (注意,对于部分方块,他们存在同名的接口。比如{@link org.bukkit.block.Chest} 和 {@link org.bukkit.block.data.type.Chest},
* 一个是BlockState的子类,一个是BlockData的子类
*
* 对于这些特殊方块,我们可以对BlockState其转型,然后获取其中的信息。
*
* API注解没提示到,这里返回的State对象是一个副本,你针对副本的修改不会立即生效。
* 而且这意味着其它插件可能会更改方块的状态, 而你的插件不会知道其变更;
* 或者其它插件可能会将方块更改为另一种类型, 从而导致你的BlockState失效.
* 所以需要在最后通过 {@link BlockState#update()} 来更新方块的状态.
*/
CommandBlock state = (CommandBlock) block.getState(); // 转型为子类
/**
* 比如在这,我们就可以获取到命令方块的命令。
* 这些方块内部的详细数据,都是由{@link org.bukkit.block.BlockState}来存储的。
* 并不是{@link org.bukkit.block.data.BlockData}
*/
String command = state.getCommand(); // 获取命令方块里的命令
player.sendMessage("你点击的方块的命令是:" + command);
/**
* 同时我们还可以修改BlockState,但是任何有关BlockState的修改操作,
* 都需要调用{@link BlockState#update()} 来完成对方块的更新。
* 这也是为什么在第#14期视频里,我们用到了该方法。
*
* 这种做法也会导致触发方块更新,因此update方法也有个applyPhysics参数
* {@link BlockState#update(boolean, boolean)}
*/
state.setCommand("/say hello"); // 修改BlockState
player.sendMessage("现在它的命令变成/say hello了,你看看是不 是。");
state.update(); // 更新BlockState
// BlockState的更新也可以触发方块更新,因此update也有个带有applyPhysic参数的同名方法
// state.update(true, false); // 强制更新方块状态,并不会引发周围方块的方块更新
} else {
player.sendMessage("你点击的方块不是命令方块");
}
}
}