mirror of
https://github.com/BentoBoxWorld/Limits.git
synced 2025-02-16 20:31:22 +01:00
Uses multi-threaded island scanning.
Optimized for Paper. Requires BentoBox 1.9.0 or later. Makes admin calc command alias recount. https://github.com/BentoBoxWorld/Limits/issues/52
This commit is contained in:
parent
d97fa9b9ae
commit
af59047f57
4
pom.xml
4
pom.xml
@ -46,13 +46,13 @@
|
||||
<powermock.version>2.0.2</powermock.version>
|
||||
<!-- More visible way how to change dependency versions -->
|
||||
<spigot.version>1.13.2-R0.1-SNAPSHOT</spigot.version>
|
||||
<bentobox.version>1.6.0</bentobox.version>
|
||||
<bentobox.version>1.9.0-SNAPSHOT</bentobox.version>
|
||||
<!-- Revision variable removes warning about dynamic version -->
|
||||
<revision>${build.version}-SNAPSHOT</revision>
|
||||
<!-- Do not change unless you want different name for local builds. -->
|
||||
<build.number>-LOCAL</build.number>
|
||||
<!-- This allows to change between versions. -->
|
||||
<build.version>1.7.0</build.version>
|
||||
<build.version>1.9.0</build.version>
|
||||
</properties>
|
||||
|
||||
<!-- Profiles will allow to automatically change build version. -->
|
||||
|
@ -24,7 +24,7 @@ public class CalcCommand extends CompositeCommand {
|
||||
* @param addon - addon
|
||||
*/
|
||||
public CalcCommand(Limits addon, CompositeCommand parent) {
|
||||
super(parent, "calc");
|
||||
super(parent, "calc", "recount");
|
||||
this.addon = addon;
|
||||
}
|
||||
|
||||
@ -60,7 +60,7 @@ public class CalcCommand extends CompositeCommand {
|
||||
}
|
||||
}
|
||||
|
||||
public void calcLimits(UUID targetPlayer, User sender) {
|
||||
private void calcLimits(UUID targetPlayer, User sender) {
|
||||
if (addon.getIslands().getIsland(getWorld(), targetPlayer) != null) {
|
||||
new LimitsCalc(getWorld(), getPlugin(), targetPlayer, addon, sender);
|
||||
} else {
|
||||
|
@ -2,6 +2,7 @@ package bentobox.addon.limits.commands;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@ -58,8 +59,8 @@ public class LimitPanel {
|
||||
.put(Material.BEETROOTS, Material.BEETROOT)
|
||||
.put(Material.REDSTONE_WIRE, Material.REDSTONE);
|
||||
// Block to Material icons
|
||||
Optional.ofNullable(Material.getMaterial("SWEET_BERRY_BUSH")).ifPresent(material -> builder.put(material, Material.getMaterial("SWEET_BERRIES")));
|
||||
Optional.ofNullable(Material.getMaterial("BAMBOO_SAPLING")).ifPresent(material -> builder.put(material, Material.getMaterial("BAMBOO")));
|
||||
Optional.ofNullable(Material.getMaterial("SWEET_BERRY_BUSH")).ifPresent(material -> builder.put(material, Objects.requireNonNull(Material.getMaterial("SWEET_BERRIES"))));
|
||||
Optional.ofNullable(Material.getMaterial("BAMBOO_SAPLING")).ifPresent(material -> builder.put(material, Objects.requireNonNull(Material.getMaterial("BAMBOO"))));
|
||||
B2M = builder.build();
|
||||
}
|
||||
|
||||
@ -105,7 +106,7 @@ public class LimitPanel {
|
||||
addon.getSettings().getLimits().forEach((k,v) -> {
|
||||
PanelItemBuilder pib = new PanelItemBuilder();
|
||||
pib.name(Util.prettifyText(k.toString()));
|
||||
Material m = Material.BARRIER;
|
||||
Material m;
|
||||
try {
|
||||
if (E2M.containsKey(k)) {
|
||||
m = E2M.get(k);
|
||||
|
@ -1,16 +1,13 @@
|
||||
package bentobox.addon.limits.commands;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChunkSnapshot;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import bentobox.addon.limits.Limits;
|
||||
import bentobox.addon.limits.listeners.BlockLimitsListener;
|
||||
@ -19,71 +16,47 @@ import world.bentobox.bentobox.BentoBox;
|
||||
import world.bentobox.bentobox.api.user.User;
|
||||
import world.bentobox.bentobox.database.objects.Island;
|
||||
import world.bentobox.bentobox.util.Pair;
|
||||
import world.bentobox.bentobox.util.Util;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author YellowZaki
|
||||
* @author YellowZaki, tastybento
|
||||
*/
|
||||
public class LimitsCalc {
|
||||
|
||||
private boolean checking;
|
||||
private Limits addon;
|
||||
private World world;
|
||||
private Island island;
|
||||
private BlockLimitsListener bll;
|
||||
private final Limits addon;
|
||||
private final World world;
|
||||
private final Island island;
|
||||
private final BlockLimitsListener bll;
|
||||
private IslandBlockCount ibc;
|
||||
private Map<Material, Integer> blockCount;
|
||||
private BukkitTask task;
|
||||
private User sender;
|
||||
private final Map<Material, AtomicInteger> blockCount;
|
||||
private final User sender;
|
||||
private final Set<Pair<Integer, Integer>> chunksToScan;
|
||||
private int count;
|
||||
|
||||
|
||||
LimitsCalc(World world, BentoBox instance, UUID targetPlayer, Limits addon, User sender) {
|
||||
this.checking = true;
|
||||
this.addon = addon;
|
||||
this.world = world;
|
||||
this.island = instance.getIslands().getIsland(world, targetPlayer);
|
||||
this.bll = addon.getBlockLimitListener();
|
||||
this.ibc = bll.getIsland(island.getUniqueId());
|
||||
blockCount = new EnumMap<>(Material.class);
|
||||
this.sender = sender;
|
||||
Set<Pair<Integer, Integer>> chunksToScan = getChunksToScan(island);
|
||||
this.task = addon.getServer().getScheduler().runTaskTimer(addon.getPlugin(), () -> {
|
||||
Set<ChunkSnapshot> chunkSnapshot = new HashSet<>();
|
||||
if (checking) {
|
||||
Iterator<Pair<Integer, Integer>> it = chunksToScan.iterator();
|
||||
if (!it.hasNext()) {
|
||||
// Nothing left
|
||||
tidyUp();
|
||||
return;
|
||||
}
|
||||
// Add chunk snapshots to the list
|
||||
while (it.hasNext() && chunkSnapshot.size() < 200) {
|
||||
Pair<Integer, Integer> pair = it.next();
|
||||
if (!world.isChunkLoaded(pair.x, pair.z)) {
|
||||
world.loadChunk(pair.x, pair.z);
|
||||
chunkSnapshot.add(world.getChunkAt(pair.x, pair.z).getChunkSnapshot());
|
||||
world.unloadChunk(pair.x, pair.z);
|
||||
} else {
|
||||
chunkSnapshot.add(world.getChunkAt(pair.x, pair.z).getChunkSnapshot());
|
||||
}
|
||||
it.remove();
|
||||
}
|
||||
// Move to next step
|
||||
checking = false;
|
||||
checkChunksAsync(chunkSnapshot);
|
||||
}
|
||||
}, 0L, 1);
|
||||
}
|
||||
this.world = world;
|
||||
|
||||
private void checkChunksAsync(final Set<ChunkSnapshot> chunkSnapshot) {
|
||||
// Run async task to scan chunks
|
||||
addon.getServer().getScheduler().runTaskAsynchronously(addon.getPlugin(), () -> {
|
||||
for (ChunkSnapshot chunk : chunkSnapshot) {
|
||||
scanChunk(chunk);
|
||||
}
|
||||
// Nothing happened, change state
|
||||
checking = true;
|
||||
});
|
||||
// Get chunks to scan
|
||||
chunksToScan = getChunksToScan(island);
|
||||
count = 0;
|
||||
chunksToScan.forEach(c -> Util.getChunkAtAsync(world, c.x, c.z).thenAccept(ch -> {
|
||||
ChunkSnapshot snapShot = ch.getChunkSnapshot();
|
||||
Bukkit.getScheduler().runTaskAsynchronously(addon.getPlugin(), () -> {
|
||||
this.scanChunk(snapShot);
|
||||
count++;
|
||||
if (count == chunksToScan.size()) {
|
||||
this.tidyUp();
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
}
|
||||
|
||||
@ -98,7 +71,7 @@ public class LimitsCalc {
|
||||
if (chunk.getZ() * 16 + z < island.getMinProtectedZ() || chunk.getZ() * 16 + z >= island.getMinProtectedZ() + island.getProtectionRange() * 2) {
|
||||
continue;
|
||||
}
|
||||
for (int y = 0; y < island.getCenter().getWorld().getMaxHeight(); y++) {
|
||||
for (int y = 0; y < Objects.requireNonNull(island.getCenter().getWorld()).getMaxHeight(); y++) {
|
||||
Material blockData = chunk.getBlockType(x, y, z);
|
||||
// Air is free
|
||||
if (!blockData.equals(Material.AIR)) {
|
||||
@ -114,9 +87,9 @@ public class LimitsCalc {
|
||||
// md is limited
|
||||
if (bll.getMaterialLimits(world, island.getUniqueId()).containsKey(md)) {
|
||||
if (!blockCount.containsKey(md)) {
|
||||
blockCount.put(md, 1);
|
||||
blockCount.put(md, new AtomicInteger(1));
|
||||
} else {
|
||||
blockCount.put(md, blockCount.get(md) + 1);
|
||||
blockCount.get(md).getAndIncrement();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -133,14 +106,15 @@ public class LimitsCalc {
|
||||
}
|
||||
|
||||
private void tidyUp() {
|
||||
// Cancel
|
||||
task.cancel();
|
||||
if (ibc == null) {
|
||||
ibc = new IslandBlockCount();
|
||||
}
|
||||
ibc.setBlockCount(blockCount);
|
||||
ibc.setBlockCount(blockCount.entrySet().stream()
|
||||
.collect(Collectors.toMap(
|
||||
Map.Entry::getKey,
|
||||
entry -> entry.getValue().get())));
|
||||
bll.setIsland(island.getUniqueId(), ibc);
|
||||
sender.sendMessage("admin.limits.calc.finished");
|
||||
Bukkit.getScheduler().runTask(addon.getPlugin(), () -> sender.sendMessage("admin.limits.calc.finished"));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,13 +1,6 @@
|
||||
package bentobox.addon.limits.listeners;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
@ -98,19 +91,19 @@ public class BlockLimitsListener implements Listener {
|
||||
addon.log("Loading default limits");
|
||||
if (addon.getConfig().isConfigurationSection("blocklimits")) {
|
||||
ConfigurationSection limitConfig = addon.getConfig().getConfigurationSection("blocklimits");
|
||||
defaultLimitMap = loadLimits(limitConfig);
|
||||
defaultLimitMap = loadLimits(Objects.requireNonNull(limitConfig));
|
||||
}
|
||||
|
||||
// Load specific worlds
|
||||
if (addon.getConfig().isConfigurationSection("worlds")) {
|
||||
ConfigurationSection worlds = addon.getConfig().getConfigurationSection("worlds");
|
||||
for (String worldName : worlds.getKeys(false)) {
|
||||
for (String worldName : Objects.requireNonNull(worlds).getKeys(false)) {
|
||||
World world = Bukkit.getWorld(worldName);
|
||||
if (world != null && addon.inGameModeWorld(world)) {
|
||||
addon.log("Loading limits for " + world.getName());
|
||||
worldLimitMap.putIfAbsent(world, new HashMap<>());
|
||||
ConfigurationSection matsConfig = worlds.getConfigurationSection(worldName);
|
||||
worldLimitMap.put(world, loadLimits(matsConfig));
|
||||
worldLimitMap.put(world, loadLimits(Objects.requireNonNull(matsConfig)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -156,7 +149,7 @@ public class BlockLimitsListener implements Listener {
|
||||
handleBreak(e, e.getPlayer(), e.getBlock());
|
||||
}
|
||||
|
||||
void handleBreak(Cancellable e, Player player, Block b) {
|
||||
private void handleBreak(Cancellable e, Player player, Block b) {
|
||||
Material mat = b.getType();
|
||||
// Special handling for crops that can break in different ways
|
||||
if (mat.equals(Material.WHEAT_SEEDS)) {
|
||||
|
@ -1,6 +1,9 @@
|
||||
package bentobox.addon.limits.listeners;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
@ -41,7 +44,7 @@ public class EntityLimitListener implements Listener {
|
||||
}
|
||||
if (addon.getSettings().getLimits().containsKey(e.getVehicle().getType())) {
|
||||
// If someone in that area has the bypass permission, allow the spawning
|
||||
for (Entity entity : e.getVehicle().getLocation().getWorld().getNearbyEntities(e.getVehicle().getLocation(), 5, 5, 5)) {
|
||||
for (Entity entity : Objects.requireNonNull(e.getVehicle().getLocation().getWorld()).getNearbyEntities(e.getVehicle().getLocation(), 5, 5, 5)) {
|
||||
if (entity instanceof Player) {
|
||||
Player player = (Player)entity;
|
||||
boolean bypass = (player.isOp() || player.hasPermission(addon.getPlugin().getIWM().getPermissionPrefix(e.getVehicle().getWorld()) + MOD_BYPASS));
|
||||
@ -103,7 +106,7 @@ public class EntityLimitListener implements Listener {
|
||||
|
||||
private boolean checkByPass(Location l) {
|
||||
// If someone in that area has the bypass permission, allow the spawning
|
||||
for (Entity entity : l.getWorld().getNearbyEntities(l, 5, 5, 5)) {
|
||||
for (Entity entity : Objects.requireNonNull(l.getWorld()).getNearbyEntities(l, 5, 5, 5)) {
|
||||
if (entity instanceof Player) {
|
||||
Player player = (Player)entity;
|
||||
if (player.isOp() || player.hasPermission(addon.getPlugin().getIWM().getPermissionPrefix(l.getWorld()) + MOD_BYPASS)) {
|
||||
@ -121,8 +124,9 @@ public class EntityLimitListener implements Listener {
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onBlock(HangingPlaceEvent e) {
|
||||
Player player = e.getPlayer();
|
||||
if (player == null) return;
|
||||
addon.getIslands().getIslandAt(e.getEntity().getLocation()).ifPresent(island -> {
|
||||
boolean bypass = player.isOp() || player.hasPermission(addon.getPlugin().getIWM().getPermissionPrefix(e.getEntity().getWorld()) + MOD_BYPASS);
|
||||
boolean bypass = Objects.requireNonNull(player).isOp() || player.hasPermission(addon.getPlugin().getIWM().getPermissionPrefix(e.getEntity().getWorld()) + MOD_BYPASS);
|
||||
// Check if entity can be hung
|
||||
if (!bypass && !island.isSpawn() && atLimit(island, e.getEntity())) {
|
||||
// Not allowed
|
||||
@ -143,7 +147,9 @@ public class EntityLimitListener implements Listener {
|
||||
e.setCancelled(true);
|
||||
// If the reason is anything but because of a spawner then tell players within range
|
||||
if (!e.getSpawnReason().equals(SpawnReason.SPAWNER) && !e.getSpawnReason().equals(SpawnReason.NATURAL) && !e.getSpawnReason().equals(SpawnReason.INFECTION) && !e.getSpawnReason().equals(SpawnReason.NETHER_PORTAL) && !e.getSpawnReason().equals(SpawnReason.REINFORCEMENTS) && !e.getSpawnReason().equals(SpawnReason.SLIME_SPLIT)) {
|
||||
for (Entity ent : e.getLocation().getWorld().getNearbyEntities(e.getLocation(), 5, 5, 5)) {
|
||||
World w = e.getLocation().getWorld();
|
||||
if (w == null) return;
|
||||
for (Entity ent : w.getNearbyEntities(e.getLocation(), 5, 5, 5)) {
|
||||
if (ent instanceof Player) {
|
||||
User.getInstance(ent).sendMessage("entity-limits.hit-limit", "[entity]",
|
||||
Util.prettifyText(e.getEntityType().toString()),
|
||||
|
@ -1,6 +1,7 @@
|
||||
package bentobox.addon.limits.listeners;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.apache.commons.lang.math.NumberUtils;
|
||||
@ -141,8 +142,8 @@ public class JoinListener implements Listener {
|
||||
// Set perm-based limits
|
||||
String prefix = addon.getGameModePermPrefix(world);
|
||||
String name = addon.getGameModeName(world);
|
||||
if (!prefix.isEmpty() && !name.isEmpty()) {
|
||||
checkPerms(owner.getPlayer(), prefix + "island.limit.", island.getUniqueId(), name);
|
||||
if (!prefix.isEmpty() && !name.isEmpty() && owner.getPlayer() != null) {
|
||||
checkPerms(Objects.requireNonNull(owner.getPlayer()), prefix + "island.limit.", island.getUniqueId(), name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ worlds:
|
||||
HOPPER: 11
|
||||
|
||||
# Default entity limits within a player's island space (protected area and to island limit).
|
||||
# A limit of 5 will allow up to 5 entites in over world, 5 in nether and 5 in the end.
|
||||
# A limit of 5 will allow up to 5 entities in over world, 5 in nether and 5 in the end.
|
||||
# Affects all types of creature spawning. Also includes entities like MINECARTS.
|
||||
# Note: Only the first 49 limited blocks and entities are shown in the limits GUI.
|
||||
entitylimits:
|
||||
|
@ -19,7 +19,7 @@ admin:
|
||||
calc:
|
||||
parameters: "<player>"
|
||||
description: "recalculate the island limits for player"
|
||||
finished: "&aIsland recalc finished sucessfully!"
|
||||
finished: "&aIsland recalc finished successfully!"
|
||||
|
||||
island:
|
||||
limits:
|
||||
|
@ -1,6 +1,3 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package bentobox.addon.limits.listeners;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
@ -10,6 +7,7 @@ import static org.mockito.Mockito.when;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.anyString;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.times;
|
||||
|
||||
|
||||
import java.util.Collections;
|
||||
@ -24,7 +22,6 @@ import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.permissions.PermissionAttachmentInfo;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@ -70,11 +67,8 @@ public class JoinListenerTest {
|
||||
@Mock
|
||||
private Island island;
|
||||
|
||||
/**
|
||||
* @throws java.lang.Exception
|
||||
*/
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
public void setUp() {
|
||||
jl = new JoinListener(addon);
|
||||
// Setup addon
|
||||
when(addon.getGameModes()).thenReturn(Collections.singletonList(bskyblock));
|
||||
@ -111,13 +105,6 @@ public class JoinListenerTest {
|
||||
when(island.getOwner()).thenReturn(UUID.randomUUID());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws java.lang.Exception
|
||||
*/
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for {@link bentobox.addon.limits.listeners.JoinListener#onNewIsland(world.bentobox.bentobox.api.events.island.IslandEvent)}.
|
||||
*/
|
||||
@ -157,7 +144,7 @@ public class JoinListenerTest {
|
||||
IslandEvent e = new IslandEvent(island, null, false, null, IslandEvent.Reason.CREATED);
|
||||
jl.onNewIsland(e);
|
||||
verify(island).getWorld();
|
||||
verify(owner).getPlayer();
|
||||
verify(owner, times(2)).getPlayer();
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user