/* * CraftBook Copyright (C) 2010-2017 sk89q <http://www.sk89q.com> * CraftBook Copyright (C) 2011-2017 me4502 <http://www.me4502.com> * CraftBook Copyright (C) Contributors * * This program 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. * * 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program. If not, * see <http://www.gnu.org/licenses/>. */ package com.sk89q.craftbook.sponge.mechanics.area; import com.flowpowered.math.vector.Vector3d; import com.google.common.collect.Lists; import com.google.inject.Inject; import com.me4502.modularframework.module.Module; import com.me4502.modularframework.module.guice.ModuleConfiguration; import com.sk89q.craftbook.core.util.ConfigValue; import com.sk89q.craftbook.core.util.CraftBookException; import com.sk89q.craftbook.core.util.PermissionNode; import com.sk89q.craftbook.core.util.documentation.DocumentationProvider; import com.sk89q.craftbook.sponge.CraftBookPlugin; import com.sk89q.craftbook.sponge.mechanics.blockbags.BlockBag; import com.sk89q.craftbook.sponge.mechanics.blockbags.EmbeddedBlockBag; import com.sk89q.craftbook.sponge.mechanics.blockbags.MultiBlockBag; import com.sk89q.craftbook.sponge.util.data.mutable.EmbeddedBlockBagData; import com.sk89q.craftbook.sponge.util.BlockFilter; import com.sk89q.craftbook.sponge.util.BlockUtil; import com.sk89q.craftbook.sponge.util.SignUtil; import com.sk89q.craftbook.sponge.util.locale.TranslationsManager; import ninja.leaping.configurate.ConfigurationNode; import org.spongepowered.api.block.BlockState; import org.spongepowered.api.block.BlockTypes; import org.spongepowered.api.block.tileentity.Sign; import org.spongepowered.api.command.CommandSource; import org.spongepowered.api.data.key.Keys; import org.spongepowered.api.entity.EntityTypes; import org.spongepowered.api.entity.Item; import org.spongepowered.api.entity.living.Humanoid; import org.spongepowered.api.event.Listener; import org.spongepowered.api.event.block.InteractBlockEvent; import org.spongepowered.api.event.cause.Cause; import org.spongepowered.api.event.cause.NamedCause; import org.spongepowered.api.event.filter.cause.Named; import org.spongepowered.api.item.inventory.ItemStack; import org.spongepowered.api.service.permission.Subject; import org.spongepowered.api.text.Text; import org.spongepowered.api.util.Direction; import org.spongepowered.api.world.Location; import org.spongepowered.api.world.World; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; import javax.annotation.Nullable; @Module(id = "gate", name = "Gate", onEnable="onInitialize", onDisable="onDisable") public class Gate extends SimpleArea implements DocumentationProvider { @Inject @ModuleConfiguration public ConfigurationNode config; private ConfigValue<Integer> searchRadius = new ConfigValue<>("search-radius", "The maximum area around the sign the gate can search.", 5); private ConfigValue<Boolean> indirectAccess = new ConfigValue<>("indirect-access", "Allows toggling of gates by clicking the gate material " + "rather than the sign.", true); @Override public void onInitialize() throws CraftBookException { super.loadCommonConfig(config); super.registerCommonPermissions(); searchRadius.load(config); indirectAccess.load(config); } @Listener public void onPlayerInteract(InteractBlockEvent.Secondary.MainHand event, @Named(NamedCause.SOURCE) Humanoid human) { super.onPlayerInteract(event, human); if (indirectAccess.getValue()) { event.getTargetBlock().getLocation().ifPresent((location) -> { if (BlockUtil.doesStatePassFilters(allowedBlocks.getValue(), location.getBlock())) { if (((human instanceof Subject) && !usePermissions.hasPermission((Subject) human))) { if (human instanceof CommandSource) ((CommandSource) human).sendMessage(TranslationsManager.USE_PERMISSIONS); return; } int x = location.getBlockX(); int y = location.getBlockY(); int z = location.getBlockZ(); for (int x1 = x - searchRadius.getValue(); x1 <= x + searchRadius.getValue(); x1++) { for (int y1 = y - searchRadius.getValue(); y1 <= y + searchRadius.getValue() * 2; y1++) { for (int z1 = z - searchRadius.getValue(); z1 <= z + searchRadius.getValue(); z1++) { Location<World> searchLocation = location.getExtent().getLocation(x1, y1, z1); Optional tileEntity = searchLocation.getTileEntity(); if (SignUtil.isSign(searchLocation) && tileEntity.isPresent() && "[Gate]".equals(SignUtil.getTextRaw((Sign) tileEntity.get(), 1))) { Set<GateColumn> columns = new HashSet<>(); BlockState state = findColumns(location, columns, location.getBlock()); toggleColumns(state, (Sign) tileEntity.get(), human, columns, null); return; } } } } } }); } } private BlockState findColumns(Location<World> block, Set<GateColumn> columns, BlockState state) { int x = block.getBlockX(); int y = block.getBlockY(); int z = block.getBlockZ(); Location<World> closestColumn = null; Vector3d blockFlat = new Vector3d(block.getX(), 0, block.getZ()); for (int x1 = x - searchRadius.getValue(); x1 <= x + searchRadius.getValue(); x1++) { for (int y1 = y - searchRadius.getValue(); y1 <= y + searchRadius.getValue() * 2; y1++) { for (int z1 = z - searchRadius.getValue(); z1 <= z + searchRadius.getValue(); z1++) { if (BlockUtil.doesStatePassFilters(allowedBlocks.getValue(), block.getExtent().getBlock(x1, y1, z1))) { if(closestColumn == null) closestColumn = block.getExtent().getLocation(x1, y1, z1); else { Vector3d oldClosest = new Vector3d(closestColumn.getX(), 0, closestColumn.getZ()); Vector3d test = new Vector3d(x1, 0, z1); if(blockFlat.distanceSquared(test) < blockFlat.distanceSquared(oldClosest)) closestColumn = block.getExtent().getLocation(x1, y1, z1); } } } } } if(closestColumn != null) state = searchColumn(closestColumn, columns, state); return state; } private BlockState searchColumn(Location<World> block, Set<GateColumn> columns, BlockState state) { if (BlockUtil.doesStatePassFilters(allowedBlocks.getValue(), block.getBlock())) { GateColumn column = new GateColumn(block, allowedBlocks); Location<World> temp = column.topBlock; if(temp.getBlockType() != BlockTypes.AIR) { if(state == null) state = temp.getBlock(); if(state.equals(temp.getBlock())) columns.add(column); else return state; } while (BlockUtil.doesStatePassFilters(allowedBlocks.getValue(), temp.getBlock()) || temp.getBlockType() == BlockTypes.AIR) { if (!columns.contains(new GateColumn(temp.getRelative(Direction.NORTH), allowedBlocks))) state = searchColumn(temp.getRelative(Direction.NORTH), columns, state); if (!columns.contains(new GateColumn(temp.getRelative(Direction.SOUTH), allowedBlocks))) state = searchColumn(temp.getRelative(Direction.SOUTH), columns, state); if (!columns.contains(new GateColumn(temp.getRelative(Direction.EAST), allowedBlocks))) state = searchColumn(temp.getRelative(Direction.EAST), columns, state); if (!columns.contains(new GateColumn(temp.getRelative(Direction.WEST), allowedBlocks))) state = searchColumn(temp.getRelative(Direction.WEST), columns, state); temp = temp.getRelative(Direction.DOWN); } } return state; } private void toggleColumn(Location<World> block, Sign sign, @Nullable Humanoid human, boolean on, BlockState gateType) { Direction dir = Direction.DOWN; block = block.getRelative(dir); ItemStack blockBagItem = ItemStack.builder().fromBlockState(gateType).quantity(1).build(); BlockBag blockBag = getBlockBag(sign.getLocation()); if (on) { while (block.getBlockType() == BlockTypes.AIR) { if (blockBag.has(Lists.newArrayList(blockBagItem.copy()))) { if (blockBag.remove(Lists.newArrayList(blockBagItem.copy())).isEmpty()) { block.setBlock(gateType, Cause.of(NamedCause.source(CraftBookPlugin.spongeInst().getContainer()))); block = block.getRelative(dir); } } else { if (human != null && human instanceof CommandSource) { ((CommandSource) human).sendMessage(Text.of("Out of Blocks")); } break; } } } else { while (BlockUtil.doesStatePassFilters(allowedBlocks.getValue(), block.getBlock())) { for (ItemStack leftover : blockBag.add(Lists.newArrayList(blockBagItem.copy()))) { Item item = (Item) block.getExtent().createEntity(EntityTypes.ITEM, sign.getLocation().getPosition()); item.offer(Keys.REPRESENTED_ITEM, leftover.createSnapshot()); block.getExtent().spawnEntity(item, CraftBookPlugin.spongeInst().getCause().build()); } block.setBlockType(BlockTypes.AIR, Cause.of(NamedCause.source(CraftBookPlugin.spongeInst().getContainer()))); block = block.getRelative(dir); } } if (blockBag instanceof EmbeddedBlockBag) { sign.getLocation().offer(new EmbeddedBlockBagData((EmbeddedBlockBag) blockBag)); } } @Override public boolean triggerMechanic(Location<World> block, Sign sign, Humanoid human, Boolean forceState) { if ("[Gate]".equals(SignUtil.getTextRaw(sign, 1))) { Set<GateColumn> columns = new HashSet<>(); BlockState state = findColumns(block, columns, null); if (!columns.isEmpty()) { toggleColumns(state, sign, human, columns, forceState); } else { if (human instanceof CommandSource) ((CommandSource) human).sendMessage(Text.builder("Can't find a gate!").build()); } } else return false; return true; } private void toggleColumns(BlockState state, Sign sign, @Nullable Humanoid human, Set<GateColumn> columns, Boolean forceState) { if (state == null) { state = BlockTypes.FENCE.getDefaultState(); } for (GateColumn vec : columns) { Location<World> col = vec.getBlock(); if (forceState == null) { forceState = !BlockUtil.doesStatePassFilters(allowedBlocks.getValue(), col.getRelative(Direction.DOWN).getBlock()); } toggleColumn(col, sign, human, forceState, state); } } @Override public BlockBag getBlockBag(Location<World> location) { BlockBag mainBlockBag = super.getBlockBag(location); Location<World> next = BlockUtil.getNextMatchingSign(location, SignUtil.getBack(location), 2, this::isMechanicSign); if (next != null) { BlockBag nextBlockBag = super.getBlockBag(next); if (nextBlockBag != null) { return new MultiBlockBag(mainBlockBag, nextBlockBag); } } return mainBlockBag; } @Override public String getPath() { return "mechanics/gate"; } @Override public ConfigValue<?>[] getConfigurationNodes() { return new ConfigValue<?>[]{ allowedBlocks, allowRedstone, searchRadius, indirectAccess }; } @Override public PermissionNode[] getPermissionNodes() { return new PermissionNode[]{ createPermissions, usePermissions }; } private static final class GateColumn { Location<World> topBlock; GateColumn(Location<World> topBlock, ConfigValue<List<BlockFilter>> allowedBlocks) { while (BlockUtil.doesStatePassFilters(allowedBlocks.getValue(), topBlock.getBlock())) { topBlock = topBlock.getRelative(Direction.UP); } topBlock = topBlock.getRelative(Direction.DOWN); this.topBlock = topBlock; } public Location<World> getBlock() { return topBlock; } @Override public int hashCode() { return topBlock.getExtent().hashCode() + topBlock.getBlockX() + topBlock.getBlockZ(); } @Override public boolean equals(Object o) { return o instanceof GateColumn && ((GateColumn) o).topBlock.getX() == topBlock.getX() && ((GateColumn) o).topBlock.getZ() == topBlock.getZ(); } } @Override public String[] getValidSigns() { return new String[]{"[Gate]"}; } @Override public List<BlockFilter> getDefaultBlocks() { List<BlockFilter> states = Lists.newArrayList(); states.add(new BlockFilter("FENCE")); states.add(new BlockFilter("NETHER_BRICK_FENCE")); states.add(new BlockFilter("GLASS_PANE")); states.add(new BlockFilter("STAINED_GLASS_PANE")); states.add(new BlockFilter("IRON_BARS")); return states; } }