001/*
002 * PlotSquared, a land and world management plugin for Minecraft.
003 * Copyright (C) IntellectualSites <https://intellectualsites.com>
004 * Copyright (C) IntellectualSites team and contributors
005 *
006 * This program is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 *
011 * This program is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
014 * GNU General Public License for more details.
015 *
016 * You should have received a copy of the GNU General Public License
017 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
018 */
019package com.plotsquared.bukkit.listener;
020
021import com.google.inject.Inject;
022import com.plotsquared.bukkit.player.BukkitPlayer;
023import com.plotsquared.bukkit.util.BukkitUtil;
024import com.plotsquared.core.PlotSquared;
025import com.plotsquared.core.configuration.Settings;
026import com.plotsquared.core.database.DBFunc;
027import com.plotsquared.core.location.Location;
028import com.plotsquared.core.player.PlotPlayer;
029import com.plotsquared.core.plot.Plot;
030import com.plotsquared.core.plot.PlotArea;
031import com.plotsquared.core.plot.flag.implementations.DisablePhysicsFlag;
032import com.plotsquared.core.plot.flag.implementations.RedstoneFlag;
033import com.plotsquared.core.plot.world.PlotAreaManager;
034import com.plotsquared.core.util.PlotFlagUtil;
035import com.plotsquared.core.util.task.TaskManager;
036import com.plotsquared.core.util.task.TaskTime;
037import com.sk89q.worldedit.WorldEdit;
038import org.bukkit.Bukkit;
039import org.bukkit.Material;
040import org.bukkit.block.Block;
041import org.bukkit.block.BlockFace;
042import org.bukkit.block.data.BlockData;
043import org.bukkit.event.EventHandler;
044import org.bukkit.event.EventPriority;
045import org.bukkit.event.Listener;
046import org.bukkit.event.block.BlockPhysicsEvent;
047import org.bukkit.event.block.BlockRedstoneEvent;
048import org.checkerframework.checker.nullness.qual.NonNull;
049
050import java.util.Set;
051import java.util.UUID;
052
053@SuppressWarnings("unused")
054public class HighFreqBlockEventListener implements Listener {
055
056    private static final Set<Material> PISTONS = Set.of(
057            Material.PISTON,
058            Material.STICKY_PISTON
059    );
060    private static final Set<Material> PHYSICS_BLOCKS = Set.of(
061            Material.TURTLE_EGG,
062            Material.TURTLE_SPAWN_EGG
063    );
064
065    private final PlotAreaManager plotAreaManager;
066    private final WorldEdit worldEdit;
067
068    @Inject
069    public HighFreqBlockEventListener(final @NonNull PlotAreaManager plotAreaManager, final @NonNull WorldEdit worldEdit) {
070        this.plotAreaManager = plotAreaManager;
071        this.worldEdit = worldEdit;
072    }
073
074    public static void sendBlockChange(final org.bukkit.Location bloc, final BlockData data) {
075        TaskManager.runTaskLater(() -> {
076            String world = bloc.getWorld().getName();
077            int x = bloc.getBlockX();
078            int z = bloc.getBlockZ();
079            int distance = Bukkit.getViewDistance() * 16;
080
081            for (final PlotPlayer<?> player : PlotSquared.platform().playerManager().getPlayers()) {
082                Location location = player.getLocation();
083                if (location.getWorldName().equals(world)) {
084                    if (16 * Math.abs(location.getX() - x) / 16 > distance || 16 * Math.abs(location.getZ() - z) / 16 > distance) {
085                        continue;
086                    }
087                    ((BukkitPlayer) player).player.sendBlockChange(bloc, data);
088                }
089            }
090        }, TaskTime.ticks(3L));
091    }
092
093    @EventHandler
094    public void onRedstoneEvent(BlockRedstoneEvent event) {
095        Block block = event.getBlock();
096        Location location = BukkitUtil.adapt(block.getLocation());
097        PlotArea area = location.getPlotArea();
098        if (area == null) {
099            return;
100        }
101        Plot plot = location.getOwnedPlot();
102        if (plot == null) {
103            if (PlotFlagUtil.isAreaRoadFlagsAndFlagEquals(area, RedstoneFlag.class, false)) {
104                event.setNewCurrent(0);
105            }
106            return;
107        }
108        if (!plot.getFlag(RedstoneFlag.class)) {
109            event.setNewCurrent(0);
110            plot.debug("Redstone event was cancelled because redstone = false");
111            return;
112        }
113        if (Settings.Redstone.DISABLE_OFFLINE) {
114            boolean disable = false;
115            if (!DBFunc.SERVER.equals(plot.getOwner())) {
116                if (plot.isMerged()) {
117                    disable = true;
118                    for (UUID owner : plot.getOwners()) {
119                        if (PlotSquared.platform().playerManager().getPlayerIfExists(owner) != null) {
120                            disable = false;
121                            break;
122                        }
123                    }
124                } else {
125                    disable = PlotSquared.platform().playerManager().getPlayerIfExists(plot.getOwnerAbs()) == null;
126                }
127            }
128            if (disable) {
129                for (UUID trusted : plot.getTrusted()) {
130                    if (PlotSquared.platform().playerManager().getPlayerIfExists(trusted) != null) {
131                        disable = false;
132                        break;
133                    }
134                }
135                if (disable) {
136                    event.setNewCurrent(0);
137                    plot.debug("Redstone event was cancelled because no trusted player was in the plot");
138                    return;
139                }
140            }
141        }
142        if (Settings.Redstone.DISABLE_UNOCCUPIED) {
143            for (final PlotPlayer<?> player : PlotSquared.platform().playerManager().getPlayers()) {
144                if (plot.equals(player.getCurrentPlot())) {
145                    return;
146                }
147            }
148            event.setNewCurrent(0);
149        }
150    }
151
152    @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
153    public void onPhysicsEvent(BlockPhysicsEvent event) {
154        Block block = event.getBlock();
155        Location location = BukkitUtil.adapt(block.getLocation());
156        PlotArea area = location.getPlotArea();
157        if (area == null) {
158            return;
159        }
160        Plot plot = area.getOwnedPlotAbs(location);
161        if (plot == null) {
162            return;
163        }
164        if (event.getChangedType().hasGravity() && plot.getFlag(DisablePhysicsFlag.class)) {
165            event.setCancelled(true);
166            sendBlockChange(event.getBlock().getLocation(), event.getBlock().getBlockData());
167            plot.debug("Prevented block physics and resent block change because disable-physics = true");
168            return;
169        }
170        if (event.getChangedType() == Material.COMPARATOR) {
171            if (!plot.getFlag(RedstoneFlag.class)) {
172                event.setCancelled(true);
173                plot.debug("Prevented comparator update because redstone = false");
174            }
175            return;
176        }
177        if (PHYSICS_BLOCKS.contains(event.getChangedType())) {
178            if (plot.getFlag(DisablePhysicsFlag.class)) {
179                event.setCancelled(true);
180                plot.debug("Prevented block physics because disable-physics = true");
181            }
182            return;
183        }
184        if (Settings.Redstone.DETECT_INVALID_EDGE_PISTONS) {
185            if (PISTONS.contains(block.getType())) {
186                org.bukkit.block.data.Directional piston = (org.bukkit.block.data.Directional) block.getBlockData();
187                final BlockFace facing = piston.getFacing();
188                location = location.add(facing.getModX(), facing.getModY(), facing.getModZ());
189                Plot newPlot = area.getOwnedPlotAbs(location);
190                if (plot.equals(newPlot)) {
191                    return;
192                }
193                if (!plot.isMerged() || !plot.getConnectedPlots().contains(newPlot)) {
194                    event.setCancelled(true);
195                    plot.debug("Prevented piston update because of invalid edge piston detection");
196                }
197            }
198        }
199    }
200
201}