/* * This file is part of Skript. * * Skript is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Skript 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Skript. If not, see <http://www.gnu.org/licenses/>. * * * Copyright 2011-2014 Peter Güttinger * */ package ch.njol.skript.util; import java.util.Arrays; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.material.Directional; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; import ch.njol.util.coll.CollectionUtils; /** * TODO !Update with every version [blocks] - also update aliases-*.sk * * @author Peter Güttinger */ @SuppressWarnings("deprecation") public abstract class BlockUtils { private final static BlockFace[] torch = new BlockFace[] { null, BlockFace.WEST, BlockFace.EAST, BlockFace.NORTH, BlockFace.SOUTH, BlockFace.DOWN }; private final static BlockFace[] button = new BlockFace[] { null, BlockFace.WEST, BlockFace.EAST, BlockFace.NORTH, BlockFace.SOUTH, null, null, null, null, BlockFace.WEST, BlockFace.EAST, BlockFace.NORTH, BlockFace.SOUTH }; private final static BlockFace[] ladder = new BlockFace[] { null, null, BlockFace.SOUTH, BlockFace.NORTH, BlockFace.EAST, BlockFace.WEST }, wallSign = ladder; private final static BlockFace[] trapdoor = new BlockFace[] { BlockFace.SOUTH, BlockFace.NORTH, BlockFace.EAST, BlockFace.WEST }; private final static BlockFace[] lever = new BlockFace[] { BlockFace.UP, BlockFace.WEST, BlockFace.EAST, BlockFace.NORTH, BlockFace.SOUTH, BlockFace.DOWN, BlockFace.DOWN, BlockFace.UP, BlockFace.UP, BlockFace.WEST, BlockFace.EAST, BlockFace.NORTH, BlockFace.SOUTH, BlockFace.DOWN, BlockFace.DOWN, BlockFace.UP }; private final static BlockFace[] cocoa = new BlockFace[] { BlockFace.SOUTH, BlockFace.WEST, BlockFace.NORTH, BlockFace.EAST }; private final static BlockFace[] tripwireHook = new BlockFace[] { BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST, BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST, BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST, BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST }; private final static BlockFace[][] attached = new BlockFace[Skript.MAXBLOCKID + 1][]; static { attached[Material.TORCH.getId()] = torch; attached[Material.STONE_BUTTON.getId()] = button; attached[Material.LADDER.getId()] = ladder; attached[Material.WALL_SIGN.getId()] = wallSign; attached[Material.TRAP_DOOR.getId()] = trapdoor; attached[Material.LEVER.getId()] = lever; if (Skript.fieldExists(Material.class, "COCOA")) { attached[Material.COCOA.getId()] = cocoa; attached[Material.TRIPWIRE_HOOK.getId()] = tripwireHook; } if (Skript.fieldExists(Material.class, "WOOD_BUTTON")) attached[Material.WOOD_BUTTON.getId()] = button; } private final static BlockFace[] bed = new BlockFace[] { BlockFace.SOUTH, BlockFace.WEST, BlockFace.NORTH, BlockFace.EAST }; // not the actual facing, but a direction where fence posts should exist private final static BlockFace[] gate = new BlockFace[] { BlockFace.WEST, BlockFace.NORTH }; /** * @param b * @param type * @param dataMin The minimum data value from 0 to 15, can be -1 * @param dataMax The maximum data value from 0 to 15, can be -1 * @param applyPhysics TODO add effect that sets block without physics checks * @return Whether the block could be set successfully */ public static boolean set(final Block b, final int type, byte dataMin, byte dataMax, final boolean applyPhysics) { final boolean any = dataMin == -1 && dataMax == -1; if (dataMin == -1) dataMin = 0; if (dataMax == -1) dataMax = 15; if (type < 0 || type > Skript.MAXBLOCKID) throw new IllegalArgumentException("Invalid block type id " + type); if (dataMin < 0 || dataMin > dataMax || dataMax > 15) throw new IllegalArgumentException("Invalid data range " + dataMin + " to " + dataMax); // ATTACHABLES final BlockFace[] attach = attached[type]; if (attach != null) { if (dataMin >= attach.length) { b.setTypeIdAndData(type, (byte) Utils.random(dataMin, dataMax + 1), applyPhysics); return true; } dataMax = (byte) Math.min(dataMax, attach.length - 1); // solid blocks? if (CollectionUtils.indexOf(attach, BlockFace.DOWN, dataMin, dataMax) != -1) { if (isSolid(b.getRelative(BlockFace.DOWN).getTypeId())) { for (final byte data : CollectionUtils.permutation(dataMin, dataMax)) { if (attach[data] != BlockFace.DOWN) continue; b.setTypeIdAndData(type, data, applyPhysics); return true; } assert false; } } for (final int data : CollectionUtils.permutation(dataMin, dataMax)) { final BlockFace f = attach[data]; if (f == null) continue; if (isSolid(b.getRelative(f).getTypeId())) { b.setTypeIdAndData(type, (byte) data, applyPhysics); return true; } } // no solid blocks - any blocks at all? if (CollectionUtils.indexOf(attach, BlockFace.DOWN, dataMin, dataMax) != -1) { if (b.getRelative(BlockFace.DOWN).getType() != Material.AIR) { for (final byte data : CollectionUtils.permutation(dataMin, dataMax)) { if (attach[data] != BlockFace.DOWN) continue; b.setTypeIdAndData(type, data, applyPhysics); return true; } assert false; } } for (final int data : CollectionUtils.permutation(dataMin, dataMax)) { final BlockFace f = attach[data]; if (f == null) continue; if (b.getRelative(f).getType() != Material.AIR) { b.setTypeIdAndData(type, (byte) data, applyPhysics); return true; } } // no blocks at all - just place it in the air, who cares ^^ b.setTypeIdAndData(type, (byte) Utils.random(dataMin, dataMax + 1), applyPhysics); return true; } // DOORS if (type == Material.IRON_DOOR_BLOCK.getId() || type == Material.WOODEN_DOOR.getId()) { final int up = b.getRelative(BlockFace.UP).getTypeId(); final int down = b.getRelative(BlockFace.DOWN).getTypeId(); if (up == 0 || up == type && b.getRelative(BlockFace.UP).getData() >= 0x8) { if (dataMin >= 0x8) // top half return false; if (!isSolid(down)) return false; dataMax = (byte) Math.min(dataMax, 0x8); final byte data = (byte) Utils.random(dataMin, dataMax + 1); if (up != type) b.getRelative(BlockFace.UP).setTypeIdAndData(type, (byte) 0x8, false); b.setTypeIdAndData(type, data, applyPhysics); return true; } else if (down == 0 || down == type && b.getRelative(BlockFace.DOWN).getData() < 0x8) { if (dataMax < 0x8) // bottom half return false; if (!isSolid(b.getRelative(BlockFace.DOWN, 2).getTypeId())) return false; dataMin = (byte) Math.max(dataMin, 0x8); final byte data = (byte) Utils.random(dataMin, dataMax + 1); if (down != type) b.getRelative(BlockFace.DOWN).setTypeIdAndData(type, (byte) 0x0, false); b.setTypeIdAndData(type, data, applyPhysics); return true; } return false; } // BED if (type == Material.BED_BLOCK.getId()) { for (final byte data : CollectionUtils.permutation(dataMin, dataMax)) { final boolean head = (data & 0x8) == 0x8; final BlockFace f = bed[data & 0x3]; if (head) { if (b.getRelative(f, -1).getTypeId() != 0) continue; b.getRelative(f, -1).setTypeIdAndData(type, (byte) (data & ~0x8), false); b.setTypeIdAndData(type, data, applyPhysics); return true; } else { if (b.getRelative(f).getTypeId() != 0) continue; b.getRelative(f).setTypeIdAndData(type, (byte) (data | 0x8), false); b.setTypeIdAndData(type, data, applyPhysics); return true; } } return false; } // FENCE GATE if (type == Material.FENCE_GATE.getId()) { final boolean[] tried = new boolean[gate.length]; for (final byte data : CollectionUtils.permutation(dataMin, dataMax)) { if (tried[data & 0x1]) continue; final BlockFace f = gate[data & 0x1]; final Block b1 = b.getRelative(f), b2 = b.getRelative(f, -1); final int m1 = b1.getTypeId(), m2 = b2.getTypeId(); // 113 == nether fence if ((m1 == Material.FENCE.getId() || m1 == 113 || m1 == Material.FENCE_GATE.getId() && (b1.getData() & 0x1) == (data & 0x1)) && (m2 == Material.FENCE.getId() || m2 == 113 || m2 == Material.FENCE_GATE.getId() && (b2.getData() & 0x1) == (data & 0x1))) { b.setTypeIdAndData(type, data, applyPhysics); return true; } else { tried[data & 0x1] = true; } } b.setTypeIdAndData(type, (byte) Utils.random(dataMin, dataMax + 1), applyPhysics); return true; } // LARGE FLOWER if (type == 175) { if (b.getRelative(BlockFace.UP).getType() == Material.AIR) { final byte data = (byte) Utils.random(dataMin, dataMax + 1); b.getRelative(BlockFace.UP).setTypeIdAndData(type, (byte) (data | 0x4), false); b.setTypeIdAndData(type, (byte) (data & ~0x4), applyPhysics); return true; } } // REMIND rails? // DEFAULT b.setTypeIdAndData(type, any ? 0 : (byte) Utils.random(dataMin, dataMax + 1), applyPhysics); return true; } // Material.isSolid() treats e.g. steps as solid... // TODO !Update with every version [blocks] private final static int[] solid = { 1, 2, 3, 4, 5, 7, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 29, 33, 35, 41, 42, 43, 45, 46, 47, 48, 49, 52, 54, 56, 57, 58, 60, 61, 62, 73, 74, 79, 80, 82, 84, 86, 87, 88, 89, 91, 95, 97, 98, 99, 100, 103, 110, 112, 120, 121, 123, 124, 125, 129, 130, 133, 137, 138, 146, 152, 153, 155, 158, 159, 161, 162, 170, 172, 173, 174 }; private final static boolean[] isSolid = new boolean[Skript.MAXBLOCKID + 1]; static { for (final int i : solid) isSolid[i] = true; } public final static boolean isSolid(final int type) { if (type < 0 || type >= isSolid.length) throw new IllegalArgumentException(type + " is not a block id"); return isSolid[type]; } @SuppressWarnings("null") public static Iterable<Block> getBlocksAround(final Block b) { return Arrays.asList(b.getRelative(BlockFace.NORTH), b.getRelative(BlockFace.EAST), b.getRelative(BlockFace.SOUTH), b.getRelative(BlockFace.WEST)); } @SuppressWarnings("null") public static Iterable<BlockFace> getFaces() { return Arrays.asList(BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST); } /** * @param b A block * @return Location of the block, including its direction */ @Nullable public static Location getLocation(final @Nullable Block b) { if (b == null) return null; final Location l = b.getLocation().add(0.5, 0.5, 0.5); final Material m = b.getType(); if (Directional.class.isAssignableFrom(m.getData())) { final BlockFace f = ((Directional) m.getNewData(b.getData())).getFacing(); l.setPitch(Direction.getPitch(Math.sin(f.getModY()))); l.setYaw(Direction.getYaw(Math.atan2(f.getModZ(), f.getModX()))); } return l; } }