413 lines
18 KiB
Java
413 lines
18 KiB
Java
package bskyblock.addon.level;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
import java.util.UUID;
|
|
|
|
import org.apache.commons.lang.math.NumberUtils;
|
|
import org.bukkit.ChunkSnapshot;
|
|
import org.bukkit.Material;
|
|
import org.bukkit.World;
|
|
import org.bukkit.entity.Player;
|
|
import org.bukkit.material.MaterialData;
|
|
import org.bukkit.permissions.PermissionAttachmentInfo;
|
|
import org.bukkit.scheduler.BukkitTask;
|
|
|
|
import com.google.common.collect.HashMultiset;
|
|
import com.google.common.collect.Multiset;
|
|
import com.google.common.collect.Multiset.Entry;
|
|
import com.google.common.collect.Multisets;
|
|
|
|
import bskyblock.addon.level.event.IslandPostLevelEvent;
|
|
import bskyblock.addon.level.event.IslandPreLevelEvent;
|
|
import us.tastybento.bskyblock.Constants;
|
|
import us.tastybento.bskyblock.api.commands.User;
|
|
import us.tastybento.bskyblock.database.objects.Island;
|
|
import us.tastybento.bskyblock.util.Pair;
|
|
|
|
|
|
public class LevelCalcByChunk {
|
|
|
|
private static final int MAX_CHUNKS = 200;
|
|
private static final long SPEED = 1;
|
|
private boolean checking = true;
|
|
private BukkitTask task;
|
|
|
|
private Level addon;
|
|
|
|
private Set<Pair<Integer, Integer>> chunksToScan;
|
|
private Island island;
|
|
private World world;
|
|
private User asker;
|
|
private UUID targetPlayer;
|
|
private Results result;
|
|
|
|
// Copy the limits hashmap
|
|
HashMap<MaterialData, Integer> limitCount;
|
|
private boolean report;
|
|
private long oldLevel;
|
|
|
|
|
|
public LevelCalcByChunk(final Level addon, final Island island, final UUID targetPlayer, final User asker, final boolean report) {
|
|
this.addon = addon;
|
|
this.island = island;
|
|
this.world = island != null ? island.getCenter().getWorld() : null;
|
|
this.asker = asker;
|
|
this.targetPlayer = targetPlayer;
|
|
this.limitCount = new HashMap<>(addon.getSettings().getBlockLimits());
|
|
this.report = report;
|
|
this.oldLevel = addon.getIslandLevel(targetPlayer);
|
|
|
|
// Results go here
|
|
result = new Results();
|
|
|
|
// Get chunks to scan
|
|
chunksToScan = getChunksToScan(island);
|
|
|
|
// Start checking
|
|
checking = true;
|
|
|
|
// Start a recurring task until done or cancelled
|
|
task = addon.getServer().getScheduler().runTaskTimer(addon.getBSkyBlock(), () -> {
|
|
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() < MAX_CHUNKS) {
|
|
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, SPEED);
|
|
}
|
|
|
|
private void checkChunksAsync(final Set<ChunkSnapshot> chunkSnapshot) {
|
|
// Run async task to scan chunks
|
|
addon.getServer().getScheduler().runTaskAsynchronously(addon.getBSkyBlock(), () -> {
|
|
for (ChunkSnapshot chunk: chunkSnapshot) {
|
|
scanChunk(chunk);
|
|
}
|
|
// Nothing happened, change state
|
|
checking = true;
|
|
});
|
|
|
|
}
|
|
|
|
@SuppressWarnings("deprecation")
|
|
private void scanChunk(ChunkSnapshot chunk) {
|
|
for (int x = 0; x< 16; x++) {
|
|
// Check if the block coord is inside the protection zone and if not, don't count it
|
|
if (chunk.getX() * 16 + x < island.getMinProtectedX() || chunk.getX() * 16 + x >= island.getMinProtectedX() + island.getProtectionRange()) {
|
|
continue;
|
|
}
|
|
for (int z = 0; z < 16; z++) {
|
|
// Check if the block coord is inside the protection zone and if not, don't count it
|
|
if (chunk.getZ() * 16 + z < island.getMinProtectedZ() || chunk.getZ() * 16 + z >= island.getMinProtectedZ() + island.getProtectionRange()) {
|
|
continue;
|
|
}
|
|
|
|
for (int y = 0; y < island.getCenter().getWorld().getMaxHeight(); y++) {
|
|
Material blockType = chunk.getBlockType(x, y, z);
|
|
boolean belowSeaLevel = (addon.getSettings().getSeaHeight() > 0 && y<=addon.getSettings().getSeaHeight()) ? true : false;
|
|
// Air is free
|
|
if (!blockType.equals(Material.AIR)) {
|
|
checkBlock(blockType, chunk.getBlockData(x, y, z), belowSeaLevel);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void checkBlock(Material type, int blockData, boolean belowSeaLevel) {
|
|
// Currently, there is no alternative to using block data (Feb 2018)
|
|
@SuppressWarnings("deprecation")
|
|
MaterialData md = new MaterialData(type, (byte) blockData);
|
|
int count = limitCount(md);
|
|
if (count > 0) {
|
|
if (belowSeaLevel) {
|
|
result.underWaterBlockCount += count;
|
|
result.uwCount.add(md);
|
|
} else {
|
|
result.rawBlockCount += count;
|
|
result.mdCount.add(md);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if a block has been limited or not and whether a block has any value or not
|
|
* @param md
|
|
* @return value of the block if can be counted
|
|
*/
|
|
private int limitCount(MaterialData md) {
|
|
MaterialData generic = new MaterialData(md.getItemType());
|
|
if (limitCount.containsKey(md) && addon.getSettings().getBlockValues().containsKey(md)) {
|
|
int count = limitCount.get(md);
|
|
if (count > 0) {
|
|
limitCount.put(md, --count);
|
|
return addon.getSettings().getBlockValues().get(md);
|
|
} else {
|
|
result.ofCount.add(md);
|
|
return 0;
|
|
}
|
|
} else if (limitCount.containsKey(generic) && addon.getSettings().getBlockValues().containsKey(generic)) {
|
|
int count = limitCount.get(generic);
|
|
if (count > 0) {
|
|
limitCount.put(generic, --count);
|
|
return addon.getSettings().getBlockValues().get(generic);
|
|
} else {
|
|
result.ofCount.add(md);
|
|
return 0;
|
|
}
|
|
} else if (addon.getSettings().getBlockValues().containsKey(md)) {
|
|
return addon.getSettings().getBlockValues().get(md);
|
|
} else if (addon.getSettings().getBlockValues().containsKey(generic)) {
|
|
return addon.getSettings().getBlockValues().get(generic);
|
|
} else {
|
|
result.ncCount.add(md);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a set of all the chunks in island
|
|
* @param island
|
|
* @return
|
|
*/
|
|
private Set<Pair<Integer, Integer>> getChunksToScan(Island island) {
|
|
Set<Pair<Integer, Integer>> chunkSnapshot = new HashSet<>();
|
|
for (int x = island.getMinProtectedX(); x < (island.getMinProtectedX() + island.getProtectionRange() + 16); x += 16) {
|
|
for (int z = island.getMinProtectedZ(); z < (island.getMinProtectedZ() + island.getProtectionRange() + 16); z += 16) {
|
|
Pair<Integer, Integer> pair = new Pair<>(world.getBlockAt(x, 0, z).getChunk().getX(), world.getBlockAt(x, 0, z).getChunk().getZ());
|
|
chunkSnapshot.add(pair);
|
|
}
|
|
}
|
|
return chunkSnapshot;
|
|
}
|
|
|
|
private void tidyUp() {
|
|
// Cancel
|
|
task.cancel();
|
|
// Finalize calculations
|
|
result.rawBlockCount += (long)((double)result.underWaterBlockCount * addon.getSettings().getUnderWaterMultiplier());
|
|
// Set the death penalty
|
|
result.deathHandicap = addon.getPlayers().getDeaths(island.getOwner());
|
|
// Set final score
|
|
result.score = (result.rawBlockCount / addon.getSettings().getLevelCost()) - result.deathHandicap - island.getLevelHandicap();
|
|
// Run any modifications
|
|
// Get the permission multiplier if it is available
|
|
int levelMultiplier = 1;
|
|
Player player = addon.getServer().getPlayer(targetPlayer);
|
|
if (player != null) {
|
|
// Get permission multiplier
|
|
for (PermissionAttachmentInfo perms : player.getEffectivePermissions()) {
|
|
if (perms.getPermission().startsWith(Constants.PERMPREFIX + "island.multiplier.")) {
|
|
String spl[] = perms.getPermission().split(Constants.PERMPREFIX + "island.multiplier.");
|
|
if (spl.length > 1) {
|
|
if (!NumberUtils.isDigits(spl[1])) {
|
|
addon.getLogger().severe("Player " + player.getName() + " has permission: " + perms.getPermission() + " <-- the last part MUST be a number! Ignoring...");
|
|
} else {
|
|
// Get the max value should there be more than one
|
|
levelMultiplier = Math.max(levelMultiplier, Integer.valueOf(spl[1]));
|
|
}
|
|
}
|
|
}
|
|
// Do some sanity checking
|
|
if (levelMultiplier < 1) {
|
|
levelMultiplier = 1;
|
|
}
|
|
}
|
|
}
|
|
// Calculate how many points are required to get to the next level
|
|
long pointsToNextLevel = (addon.getSettings().getLevelCost() * (result.score + 1 + island.getLevelHandicap())) - ((result.rawBlockCount * levelMultiplier) - (result.deathHandicap * addon.getSettings().getDeathPenalty()));
|
|
// Sometimes it will return 0, so calculate again to make sure it will display a good value
|
|
if(pointsToNextLevel == 0) pointsToNextLevel = (addon.getSettings().getLevelCost() * (result.score + 2 + island.getLevelHandicap()) - ((result.rawBlockCount * levelMultiplier) - (result.deathHandicap * addon.getSettings().getDeathPenalty())));
|
|
|
|
// All done.
|
|
informPlayers(saveLevel(island, targetPlayer, pointsToNextLevel));
|
|
|
|
}
|
|
|
|
private void informPlayers(IslandPreLevelEvent event) {
|
|
// Fire the island post level calculation event
|
|
final IslandPostLevelEvent event3 = new IslandPostLevelEvent(targetPlayer, island, event.getLevel(), event.getPointsToNextLevel());
|
|
addon.getServer().getPluginManager().callEvent(event3);
|
|
|
|
if(event3.isCancelled() || asker == null) {
|
|
return;
|
|
}
|
|
// Tell the asker
|
|
asker.sendMessage("island.level.island-level-is", "[level]", String.valueOf(addon.getIslandLevel(targetPlayer)));
|
|
// Console
|
|
if (report) {
|
|
sendConsoleReport(asker);
|
|
}
|
|
// Check if player - if so show some more info
|
|
if (!(asker instanceof Player)) {
|
|
return;
|
|
}
|
|
// Player
|
|
if (addon.getSettings().getDeathPenalty() != 0) {
|
|
asker.sendMessage("island.level.deaths", "[number]", String.valueOf(result.deathHandicap));
|
|
}
|
|
// Send player how many points are required to reach next island level
|
|
if (event.getPointsToNextLevel() >= 0) {
|
|
asker.sendMessage("island.level.required-points-to-next-level", "[points]", String.valueOf(event.getPointsToNextLevel()));
|
|
}
|
|
// Tell other team members
|
|
if (addon.getIslandLevel(targetPlayer) != oldLevel) {
|
|
for (UUID member : island.getMemberSet()) {
|
|
if (!member.equals(asker.getUniqueId())) {
|
|
User.getInstance(member).sendMessage("island.level.island-level-is", "[level]", String.valueOf(addon.getIslandLevel(targetPlayer)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private IslandPreLevelEvent saveLevel(Island island, UUID targetPlayer, long pointsToNextLevel) {
|
|
// Fire the pre-level event
|
|
final IslandPreLevelEvent event = new IslandPreLevelEvent(targetPlayer, island, result.score);
|
|
event.setPointsToNextLevel(pointsToNextLevel);
|
|
addon.getServer().getPluginManager().callEvent(event);
|
|
if (!event.isCancelled()) {
|
|
// Save the value
|
|
addon.setIslandLevel(island.getOwner(), event.getLevel());
|
|
if (addon.getPlayers().inTeam(targetPlayer)) {
|
|
//plugin.getLogger().info("DEBUG: player is in team");
|
|
for (UUID member : addon.getIslands().getMembers(targetPlayer)) {
|
|
//plugin.getLogger().info("DEBUG: updating team member level too");
|
|
if (addon.getIslandLevel(member) != event.getLevel()) {
|
|
addon.setIslandLevel(member, event.getLevel());
|
|
}
|
|
}
|
|
if (addon.getPlayers().inTeam(targetPlayer)) {
|
|
UUID leader = addon.getIslands().getTeamLeader(targetPlayer);
|
|
if (leader != null) {
|
|
addon.getTopTen().addEntry(leader, event.getLevel());
|
|
}
|
|
} else {
|
|
addon.getTopTen().addEntry(targetPlayer, event.getLevel());
|
|
}
|
|
}
|
|
}
|
|
return event;
|
|
}
|
|
|
|
private void sendConsoleReport(User asker) {
|
|
List<String> reportLines = new ArrayList<>();
|
|
// provide counts
|
|
reportLines.add("Level Log for island at " + island.getCenter());
|
|
reportLines.add("Island owner UUID = " + island.getOwner());
|
|
reportLines.add("Total block value count = " + String.format("%,d",result.rawBlockCount));
|
|
reportLines.add("Level cost = " + addon.getSettings().getLevelCost());
|
|
//reportLines.add("Level multiplier = " + levelMultiplier + " (Player must be online to get a permission multiplier)");
|
|
//reportLines.add("Schematic level handicap = " + levelHandicap + " (level is reduced by this amount)");
|
|
reportLines.add("Deaths handicap = " + result.deathHandicap);
|
|
reportLines.add("Level calculated = " + result.score);
|
|
reportLines.add("==================================");
|
|
int total = 0;
|
|
if (!result.uwCount.isEmpty()) {
|
|
reportLines.add("Underwater block count (Multiplier = x" + addon.getSettings().getUnderWaterMultiplier() + ") value");
|
|
reportLines.add("Total number of underwater blocks = " + String.format("%,d",result.uwCount.size()));
|
|
reportLines.addAll(sortedReport(total, result.uwCount));
|
|
}
|
|
reportLines.add("Regular block count");
|
|
reportLines.add("Total number of blocks = " + String.format("%,d",result.mdCount.size()));
|
|
reportLines.addAll(sortedReport(total, result.mdCount));
|
|
|
|
reportLines.add("Blocks not counted because they exceeded limits: " + String.format("%,d",result.ofCount.size()));
|
|
//entriesSortedByCount = Multisets.copyHighestCountFirst(ofCount).entrySet();
|
|
Iterable<Multiset.Entry<MaterialData>> entriesSortedByCount = result.ofCount.entrySet();
|
|
Iterator<Entry<MaterialData>> it = entriesSortedByCount.iterator();
|
|
while (it.hasNext()) {
|
|
Entry<MaterialData> type = it.next();
|
|
Integer limit = addon.getSettings().getBlockLimits().get(type.getElement());
|
|
String explain = ")";
|
|
if (limit == null) {
|
|
MaterialData generic = new MaterialData(type.getElement().getItemType());
|
|
limit = addon.getSettings().getBlockLimits().get(generic);
|
|
explain = " - All types)";
|
|
}
|
|
reportLines.add(type.getElement().toString() + ": " + String.format("%,d",type.getCount()) + " blocks (max " + limit + explain);
|
|
}
|
|
reportLines.add("==================================");
|
|
reportLines.add("Blocks on island that are not in config.yml");
|
|
reportLines.add("Total number = " + String.format("%,d",result.ncCount.size()));
|
|
//entriesSortedByCount = Multisets.copyHighestCountFirst(ncCount).entrySet();
|
|
entriesSortedByCount = result.ncCount.entrySet();
|
|
it = entriesSortedByCount.iterator();
|
|
while (it.hasNext()) {
|
|
Entry<MaterialData> type = it.next();
|
|
reportLines.add(type.getElement().toString() + ": " + String.format("%,d",type.getCount()) + " blocks");
|
|
}
|
|
reportLines.add("=================================");
|
|
|
|
for (String line : reportLines) {
|
|
asker.sendRawMessage(line);
|
|
}
|
|
}
|
|
|
|
private Collection<String> sortedReport(int total, Multiset<MaterialData> materialDataCount) {
|
|
Collection<String> result = new ArrayList<>();
|
|
Iterable<Multiset.Entry<MaterialData>> entriesSortedByCount = Multisets.copyHighestCountFirst(materialDataCount).entrySet();
|
|
Iterator<Entry<MaterialData>> it = entriesSortedByCount.iterator();
|
|
while (it.hasNext()) {
|
|
Entry<MaterialData> en = it.next();
|
|
MaterialData type = en.getElement();
|
|
|
|
int value = 0;
|
|
if (addon.getSettings().getBlockValues().containsKey(type)) {
|
|
// Specific
|
|
value = addon.getSettings().getBlockValues().get(type);
|
|
} else if (addon.getSettings().getBlockValues().containsKey(new MaterialData(type.getItemType()))) {
|
|
// Generic
|
|
value = addon.getSettings().getBlockValues().get(new MaterialData(type.getItemType()));
|
|
}
|
|
if (value > 0) {
|
|
result.add(type.toString() + ":"
|
|
+ String.format("%,d",en.getCount()) + " blocks x " + value + " = " + (value * en.getCount()));
|
|
total += (value * en.getCount());
|
|
}
|
|
}
|
|
result.add("Subtotal = " + total);
|
|
result.add("==================================");
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Results class
|
|
*
|
|
*/
|
|
public class Results {
|
|
Multiset<MaterialData> mdCount = HashMultiset.create();
|
|
Multiset<MaterialData> uwCount = HashMultiset.create();
|
|
Multiset<MaterialData> ncCount = HashMultiset.create();
|
|
Multiset<MaterialData> ofCount = HashMultiset.create();
|
|
long rawBlockCount = 0;
|
|
Island island;
|
|
long underWaterBlockCount = 0;
|
|
long score = 0;
|
|
int deathHandicap = 0;
|
|
}
|
|
}
|