Merge pull request #344 from BentoBoxWorld/342_different_spawner_types

342 different spawner types
This commit is contained in:
tastybento 2025-02-22 16:56:23 -08:00 committed by GitHub
commit 8390800994
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 748 additions and 582 deletions

View File

@ -11,22 +11,22 @@ jobs:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Set up JDK 17
uses: actions/setup-java@v3
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: 'adopt'
java-version: 17
java-version: '21'
- name: Cache SonarCloud packages
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar
- name: Cache Maven packages
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}

View File

@ -50,12 +50,12 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>17</java.version>
<java.version>21</java.version>
<!-- Non-minecraft related dependencies -->
<powermock.version>2.0.9</powermock.version>
<!-- More visible way how to change dependency versions -->
<spigot.version>1.21.3-R0.1-SNAPSHOT</spigot.version>
<bentobox.version>2.7.1-SNAPSHOT</bentobox.version>
<bentobox.version>3.2.4-SNAPSHOT</bentobox.version>
<!-- Warps addon version -->
<warps.version>1.12.0</warps.version>
<!-- Visit addon version -->
@ -67,7 +67,7 @@
<!-- Do not change unless you want different name for local builds. -->
<build.number>-LOCAL</build.number>
<!-- This allows to change between versions. -->
<build.version>2.17.1</build.version>
<build.version>2.18.0</build.version>
<sonar.projectKey>BentoBoxWorld_Level</sonar.projectKey>
<sonar.organization>bentobox-world</sonar.organization>
<sonar.host.url>https://sonarcloud.io</sonar.host.url>

View File

@ -106,7 +106,7 @@ public class Level extends Addon {
// Register listeners
this.registerListener(new IslandActivitiesListeners(this));
this.registerListener(new JoinLeaveListener(this));
this.registerListener(new MigrationListener(this));
this.registerListener(new MigrationListener(this));
// Register commands for GameModes
registeredGameModes.clear();

View File

@ -30,7 +30,6 @@ import world.bentobox.level.calculators.Results;
import world.bentobox.level.events.IslandLevelCalculatedEvent;
import world.bentobox.level.events.IslandPreLevelEvent;
import world.bentobox.level.objects.IslandLevels;
import world.bentobox.level.objects.LevelsData;
import world.bentobox.level.objects.TopTenData;
import world.bentobox.level.util.CachedData;
@ -67,38 +66,6 @@ public class LevelsManager {
}
public void migrate() {
Database<LevelsData> oldDb = new Database<>(addon, LevelsData.class);
oldDb.loadObjects().forEach(ld -> {
try {
UUID owner = UUID.fromString(ld.getUniqueId());
// Step through each world
ld.getLevels().keySet().stream()
// World
.map(Bukkit::getWorld).filter(Objects::nonNull)
// Island
.map(w -> addon.getIslands().getIsland(w, owner)).filter(Objects::nonNull).forEach(i -> {
// Make new database entry
World w = i.getWorld();
IslandLevels il = new IslandLevels(i.getUniqueId());
il.setInitialLevel(ld.getInitialLevel(w));
il.setLevel(ld.getLevel(w));
il.setMdCount(ld.getMdCount(w));
il.setPointsToNextLevel(ld.getPointsToNextLevel(w));
il.setUwCount(ld.getUwCount(w));
// Save it
handler.saveObjectAsync(il);
});
// Now delete the old database entry
oldDb.deleteID(ld.getUniqueId());
} catch (Exception e) {
addon.logError("Could not migrate level data database! " + e.getMessage());
e.printStackTrace();
return;
}
});
}
/**
* Add an island to a top ten
*
@ -289,6 +256,7 @@ public class LevelsManager {
if (ld != null) {
levelsCache.put(id, ld);
} else {
// Clean up just in case
handler.deleteID(id);
levelsCache.put(id, new IslandLevels(id));
}

View File

@ -2,14 +2,15 @@ package world.bentobox.level.calculators;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.Set;
import java.util.UUID;
@ -31,9 +32,9 @@ import org.bukkit.block.CreatureSpawner;
import org.bukkit.block.ShulkerBox;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.type.Slab;
import org.bukkit.entity.EntityType;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BlockStateMeta;
import org.bukkit.scheduler.BukkitTask;
import com.bgsoftware.wildstacker.api.WildStackerAPI;
import com.bgsoftware.wildstacker.api.objects.StackedBarrel;
@ -55,24 +56,11 @@ import world.bentobox.level.calculators.Results.Result;
public class IslandLevelCalculator {
private static final String LINE_BREAK = "==================================";
public static final long MAX_AMOUNT = 10000000;
private static final List<Material> CHESTS = Arrays.asList(Material.CHEST, Material.CHEST_MINECART,
Material.TRAPPED_CHEST, Material.SHULKER_BOX, Material.BLACK_SHULKER_BOX, Material.BLUE_SHULKER_BOX,
Material.BROWN_SHULKER_BOX, Material.CYAN_SHULKER_BOX, Material.GRAY_SHULKER_BOX,
Material.GREEN_SHULKER_BOX, Material.LIGHT_BLUE_SHULKER_BOX, Material.LIGHT_GRAY_SHULKER_BOX,
Material.LIME_SHULKER_BOX, Material.MAGENTA_SHULKER_BOX, Material.ORANGE_SHULKER_BOX,
Material.PINK_SHULKER_BOX, Material.PURPLE_SHULKER_BOX, Material.RED_SHULKER_BOX, Material.RED_SHULKER_BOX,
Material.WHITE_SHULKER_BOX, Material.YELLOW_SHULKER_BOX, Material.COMPOSTER, Material.BARREL,
Material.DISPENSER, Material.DROPPER, Material.SMOKER, Material.BLAST_FURNACE, Material.BUNDLE,
Material.RED_BUNDLE, Material.BLACK_BUNDLE, Material.BLUE_BUNDLE, Material.BROWN_BUNDLE,
Material.CYAN_BUNDLE, Material.GRAY_BUNDLE, Material.GREEN_BUNDLE, Material.LIGHT_BLUE_BUNDLE,
Material.LIGHT_GRAY_BUNDLE, Material.LIME_BUNDLE, Material.MAGENTA_BUNDLE, Material.ORANGE_BUNDLE,
Material.PINK_BUNDLE, Material.PURPLE_BUNDLE, Material.RED_BUNDLE, Material.WHITE_BUNDLE,
Material.YELLOW_BUNDLE);
private static final int CHUNKS_TO_SCAN = 100;
private final Level addon;
private final Queue<Pair<Integer, Integer>> chunksToCheck;
private final Island island;
private final Map<Material, Integer> limitCount;
private final Map<Object, Integer> limitCount;
private final CompletableFuture<Results> r;
private final Results results;
@ -82,7 +70,7 @@ public class IslandLevelCalculator {
private final int seaHeight;
private final List<Location> stackedBlocks = new ArrayList<>();
private final Set<Chunk> chestBlocks = new HashSet<>();
private BukkitTask finishTask;
private final Map<Location, Boolean> spawners = new HashMap<>();
/**
* Constructor to get the level for an island
@ -101,7 +89,7 @@ public class IslandLevelCalculator {
results = new Results();
duration = System.currentTimeMillis();
chunksToCheck = getChunksToScan(island);
this.limitCount = new EnumMap<>(addon.getBlockConfig().getBlockLimits());
this.limitCount = new HashMap<>();
// Get the initial island level
results.initialLevel.set(addon.getInitialIslandLevel(island));
// Set up the worlds
@ -163,6 +151,26 @@ public class IslandLevelCalculator {
}
}
/**
* Adds value to the results based on the material and whether the block is
* below sea level or not
*
* @param mat - material of the block
* @param belowSeaLevel - true if below sea level
*/
private void checkSpawner(EntityType et, boolean belowSeaLevel) {
Integer count = limitCount(et);
if (count != null) {
if (belowSeaLevel) {
results.underWaterBlockCount.addAndGet(count);
results.uwCount.add(et);
} else {
results.rawBlockCount.addAndGet(count);
results.mdCount.add(et);
}
}
}
/**
* Get a set of all the chunks in island
*
@ -231,31 +239,17 @@ public class IslandLevelCalculator {
reportLines.add(
"Blocks not counted because they exceeded limits: " + String.format("%,d", results.ofCount.size()));
Iterable<Multiset.Entry<Material>> entriesSortedByCount = results.ofCount.entrySet();
Iterator<Entry<Material>> it = entriesSortedByCount.iterator();
Iterable<Multiset.Entry<Object>> entriesSortedByCount = results.ofCount.entrySet();
Iterator<Entry<Object>> it = entriesSortedByCount.iterator();
while (it.hasNext()) {
Entry<Material> type = it.next();
Integer limit = addon.getBlockConfig().getBlockLimits().get(type.getElement());
Entry<Object> type = it.next();
Integer limit = addon.getBlockConfig().getLimit(type);
String explain = ")";
if (limit == null) {
Material generic = type.getElement();
limit = addon.getBlockConfig().getBlockLimits().get(generic);
explain = " - All types)";
}
reportLines.add(type.getElement().toString() + ": " + String.format("%,d", type.getCount())
reportLines.add(Util.prettifyText(type.toString()) + ": " + String.format("%,d", type.getCount())
+ " blocks (max " + limit + explain);
}
reportLines.add(LINE_BREAK);
reportLines.add("Blocks on island that are not in config.yml");
reportLines.add("Total number = " + String.format("%,d", results.ncCount.size()));
entriesSortedByCount = results.ncCount.entrySet();
it = entriesSortedByCount.iterator();
while (it.hasNext()) {
Entry<Material> type = it.next();
reportLines.add(type.getElement().toString() + ": " + String.format("%,d", type.getCount()) + " blocks");
}
reportLines.add(LINE_BREAK);
return reportLines;
}
@ -269,10 +263,10 @@ public class IslandLevelCalculator {
/**
* Get value of a material World blocks trump regular block values
*
* @param md - Material to check
* @return value of a material
* @param md - Material or EntityType to check
* @return value
*/
private int getValue(Material md) {
private int getValue(Object md) {
Integer value = addon.getBlockConfig().getValue(island.getWorld(), md);
if (value == null) {
// Not in config
@ -333,24 +327,28 @@ public class IslandLevelCalculator {
}
/**
* Checks if a block has been limited or not and whether a block has any value
* or not
*
* @param md Material
* @return value of the block if can be counted
* Checks if the given object (Material or EntityType) has reached its limit.
* If it hasn't, the object's value is returned and its count is incremented.
* If the object is not a Material or EntityType, or if it has exceeded its limit, 0 is returned.
*
* @param obj A Material or EntityType
* @return The object's value if within limit, otherwise 0.
*/
private int limitCount(Material md) {
if (limitCount.containsKey(md)) {
int count = limitCount.get(md);
if (count > 0) {
limitCount.put(md, --count);
return getValue(md);
} else {
results.ofCount.add(md);
return 0;
}
}
return getValue(md);
private int limitCount(Object obj) {
// Only process if obj is a Material or EntityType
if (!(obj instanceof Material) && !(obj instanceof EntityType))
return 0;
Integer limit = addon.getBlockConfig().getLimit(obj);
if (limit == null)
return getValue(obj);
int count = limitCount.getOrDefault(obj, 0);
if (count > limit)
return 0;
limitCount.put(obj, count + 1);
return getValue(obj);
}
/**
@ -454,6 +452,8 @@ public class IslandLevelCalculator {
BlockData blockData = cp.chunkSnapshot.getBlockData(x, y, z);
Material m = blockData.getMaterial();
boolean belowSeaLevel = seaHeight > 0 && y <= seaHeight;
Location loc = new Location(cp.world, (double) x + cp.chunkSnapshot.getX() * 16, y,
(double) z + cp.chunkSnapshot.getZ() * 16);
// Slabs can be doubled, so check them twice
if (Tag.SLABS.isTagged(m)) {
Slab slab = (Slab) blockData;
@ -464,22 +464,26 @@ public class IslandLevelCalculator {
// Hook for Wild Stackers (Blocks and Spawners Only) - this has to use the real
// chunk
if (addon.isStackersEnabled() && (m.equals(Material.CAULDRON) || m.equals(Material.SPAWNER))) {
stackedBlocks.add(new Location(cp.world, (double) x + cp.chunkSnapshot.getX() * 16, y,
(double) z + cp.chunkSnapshot.getZ() * 16));
stackedBlocks.add(loc);
}
if (addon.isUltimateStackerEnabled() && !m.isAir()) {
Location l = new Location(cp.chunk.getWorld(), x, y, z);
UltimateStackerCalc.addStackers(m, l, results, belowSeaLevel, limitCount(m));
UltimateStackerCalc.addStackers(m, loc, results, belowSeaLevel, limitCount(m));
}
// Scan chests
if (addon.getSettings().isIncludeChests() && CHESTS.contains(m)) {
if (addon.getSettings().isIncludeChests() && blockData instanceof Container) {
chestBlocks.add(cp.chunk);
}
// Add the value of the block's material
checkBlock(m, belowSeaLevel);
// Spawners
if (m == Material.SPAWNER) {
// Stash the spawner because the type cannot be obtained from the chunk snapshot
this.spawners.put(loc, belowSeaLevel);
} else {
// Add the value of the block's material
checkBlock(m, belowSeaLevel);
}
}
}
}
@ -520,16 +524,23 @@ public class IslandLevelCalculator {
return result;
}
private Collection<String> sortedReport(int total, Multiset<Material> materialCount) {
private Collection<String> sortedReport(int total, Multiset<Object> uwCount) {
Collection<String> result = new ArrayList<>();
Iterable<Multiset.Entry<Material>> entriesSortedByCount = Multisets.copyHighestCountFirst(materialCount)
Iterable<Multiset.Entry<Object>> entriesSortedByCount = Multisets.copyHighestCountFirst(uwCount)
.entrySet();
for (Entry<Material> en : entriesSortedByCount) {
Material type = en.getElement();
for (Entry<Object> en : entriesSortedByCount) {
int value = getValue(type);
int value = 0;
String name = "";
if (en.getElement() instanceof Material md) {
value = Objects.requireNonNullElse(addon.getBlockConfig().getValue(island.getWorld(), md), 0);
name = Util.prettifyText(md.name());
} else if (en.getElement() instanceof EntityType et) {
name = Util.prettifyText(et.name());
value = Objects.requireNonNullElse(addon.getBlockConfig().getValue(island.getWorld(), et), 0);
}
result.add(type.toString() + ":" + String.format("%,d", en.getCount()) + " blocks x " + value + " = "
result.add(name + " :" + String.format("%,d", en.getCount()) + " blocks x " + value + " = "
+ (value * en.getCount()));
total += (value * en.getCount());
@ -616,8 +627,9 @@ public class IslandLevelCalculator {
pipeliner.getInProcessQueue().remove(this);
BentoBox.getInstance().log("Completed Level scan.");
// Chunk finished
// This was the last chunk. Handle stacked blocks, then chests and exit
handleStackedBlocks().thenCompose(v -> handleChests()).thenRun(() -> {
// This was the last chunk. Handle stacked blocks, spawners, chests and exit
handleStackedBlocks().thenCompose(v -> handleSpawners()).thenCompose(v -> handleChests())
.thenRun(() -> {
this.tidyUp();
this.getR().complete(getResults());
});
@ -625,6 +637,27 @@ public class IslandLevelCalculator {
});
}
private CompletableFuture<Void> handleSpawners() {
List<CompletableFuture<Void>> futures = new ArrayList<>();
for (Map.Entry<Location, Boolean> en : this.spawners.entrySet()) {
CompletableFuture<Void> future = Util.getChunkAtAsync(en.getKey()).thenAccept(c -> {
if (en.getKey().getBlock().getType() == Material.SPAWNER) {
EntityType et = ((CreatureSpawner) en.getKey().getBlock().getState()).getSpawnedType();
if (et != null) {
checkSpawner(et, en.getValue());
} else {
// This spawner has no spawning capability. Just list it as a spawner block
checkBlock(Material.SPAWNER, en.getValue());
}
}
});
futures.add(future);
}
// Return a CompletableFuture that completes when all futures are done
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
}
private CompletableFuture<Void> handleChests() {
List<CompletableFuture<Void>> futures = new ArrayList<>();
for (Chunk v : chestBlocks) {

View File

@ -4,8 +4,6 @@ import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.bukkit.Material;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.Multiset;
@ -25,10 +23,10 @@ public class Results {
TIMEOUT
}
List<String> report;
final Multiset<Material> mdCount = HashMultiset.create();
final Multiset<Material> uwCount = HashMultiset.create();
final Multiset<Material> ncCount = HashMultiset.create();
final Multiset<Material> ofCount = HashMultiset.create();
final Multiset<Object> mdCount = HashMultiset.create();
final Multiset<Object> uwCount = HashMultiset.create();
final Multiset<Object> ncCount = HashMultiset.create();
final Multiset<Object> ofCount = HashMultiset.create();
// AtomicLong and AtomicInteger must be used because they are changed by multiple concurrent threads
AtomicLong rawBlockCount = new AtomicLong(0);
AtomicLong underWaterBlockCount = new AtomicLong(0);
@ -130,13 +128,13 @@ public class Results {
/**
* @return the mdCount
*/
public Multiset<Material> getMdCount() {
public Multiset<Object> getMdCount() {
return mdCount;
}
/**
* @return the uwCount
*/
public Multiset<Material> getUwCount() {
public Multiset<Object> getUwCount() {
return uwCount;
}
/**

View File

@ -6,8 +6,6 @@ import org.bukkit.Material;
import com.craftaro.ultimatestacker.api.UltimateStackerApi;
import com.craftaro.ultimatestacker.api.utils.Stackable;
import world.bentobox.bentobox.BentoBox;
/**
* Isolates UltimateStacker imports so that they are only loaded if the plugin exists
*/

View File

@ -123,8 +123,8 @@ public class IslandValueCommand extends CompositeCommand
int count = lvData.getMdCount().getOrDefault(material, 0) + lvData.getUwCount().getOrDefault(material, 0);
user.sendMessage("level.conversations.you-have", TextVariables.NUMBER,
String.valueOf(count));
int limit = this.addon.getBlockConfig().getBlockLimits().getOrDefault(material, -1);
if (limit > 0) {
Integer limit = this.addon.getBlockConfig().getLimit(material);
if (limit != null) {
user.sendMessage("level.conversations.you-can-place", TextVariables.NUMBER,
String.valueOf(limit));
}

View File

@ -2,8 +2,6 @@ package world.bentobox.level.config;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
@ -17,6 +15,7 @@ import org.bukkit.Registry;
import org.bukkit.World;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.EntityType;
import world.bentobox.level.Level;
@ -27,10 +26,13 @@ import world.bentobox.level.Level;
*/
public class BlockConfig {
private Map<Material, Integer> blockLimits = new EnumMap<>(Material.class);
private static final String SPAWNER = "_SPAWNER";
private Map<String, Integer> blockLimits = new HashMap<>();
private Map<Material, Integer> blockValues = new EnumMap<>(Material.class);
private final Map<World, Map<Material, Integer>> worldBlockValues = new HashMap<>();
private final Map<World, Map<EntityType, Integer>> worldSpawnerValues = new HashMap<>();
private final List<Material> hiddenBlocks;
private Map<EntityType, Integer> spawnerValues = new EnumMap<>(EntityType.class);
private Level addon;
/**
@ -43,10 +45,13 @@ public class BlockConfig {
public BlockConfig(Level addon, YamlConfiguration blockValues, File file) throws IOException {
this.addon = addon;
if (blockValues.isConfigurationSection("limits")) {
setBlockLimits(loadBlockLimits(blockValues));
for (String key : blockValues.getConfigurationSection("limits").getKeys(false)) {
blockLimits.put(key, blockValues.getConfigurationSection("limits").getInt(key));
}
}
if (blockValues.isConfigurationSection("blocks")) {
setBlockValues(loadBlockValues(blockValues));
setSpawnerValues(loadSpawnerValues(blockValues));
} else {
addon.logWarning("No block values in blockconfig.yml! All island levels will be zero!");
}
@ -58,12 +63,12 @@ public class BlockConfig {
hiddenBlocks = blockValues.getStringList("hidden-blocks").stream().map(name -> {
try {
return Material.valueOf(name.toUpperCase(Locale.ENGLISH));
} catch (Exception e) {
return null;
}
}).filter(Objects::nonNull).toList();
// All done
blockValues.save(file);
}
@ -110,31 +115,50 @@ public class BlockConfig {
return bv;
}
private Map<Material, Integer> loadBlockLimits(YamlConfiguration blockValues2) {
Map<Material, Integer> bl = new EnumMap<>(Material.class);
ConfigurationSection limits = Objects.requireNonNull(blockValues2.getConfigurationSection("limits"));
for (String material : limits.getKeys(false)) {
try {
Material mat = Material.valueOf(material);
bl.put(mat, limits.getInt(material, 0));
} catch (Exception e) {
addon.logError("Unknown material (" + material + ") in blockconfig.yml Limits section. Skipping...");
/**
* Loads the spawner values from the blocks in the config
* Format is entityname + _SPANWER, so for example ALLAY_SPAWNER
* If they are missing, then they will score 1
* @param blockValues config section
* @return map of entity types and their score
*/
private Map<EntityType, Integer> loadSpawnerValues(YamlConfiguration blockValues) {
ConfigurationSection blocks = Objects.requireNonNull(blockValues.getConfigurationSection("blocks"));
Map<EntityType, Integer> bv = new HashMap<>();
// Update spawners
Registry.MATERIAL.stream().filter(Material::isItem)
.filter(m -> m.name().endsWith("_SPAWN_EGG")).map(m -> m.name().substring(0, m.name().length() - 10))
.forEach(m -> {
// Populate missing spawners
if (!blocks.contains(m + SPAWNER, true)) {
blocks.set(m + SPAWNER, 1);
}
// Load value
try {
EntityType et = EntityType.valueOf(m);
bv.put(et, blocks.getInt(m + SPAWNER));
} catch (Exception e) {
e.printStackTrace();
}
}
return bl;
});
return bv;
}
/**
* @return the blockLimits
* Return the limits for any particular material or entity type
* @param obj material or entity type
* @return the limit or null if there isn't one
*/
public final Map<Material, Integer> getBlockLimits() {
return blockLimits;
}
/**
* @param bl the blockLimits to set
*/
private void setBlockLimits(Map<Material, Integer> bl) {
this.blockLimits = bl;
public Integer getLimit(Object obj) {
if (obj instanceof Material m) {
return blockLimits.get(m.name());
}
if (obj instanceof EntityType et) {
return blockLimits.get(et.name().concat(SPAWNER));
}
return null;
}
/**
* @return the blockValues
@ -157,39 +181,78 @@ public class BlockConfig {
}
/**
* Get the value of material in world
* @return the worldSpawnerValues
*/
public Map<World, Map<EntityType, Integer>> getWorldSpawnerValues() {
return worldSpawnerValues;
}
/**
* Get the value of of a spawner in world
* @param world - world
* @param md - material
* @param obj - entity type that will spawn from this spawner or material
* @return value or null if not configured with a value
*/
public Integer getValue(World world, Material md) {
// Check world settings
if (getWorldBlockValues().containsKey(world) && getWorldBlockValues().get(world).containsKey(md)) {
return getWorldBlockValues().get(world).get(md);
}
// Check baseline
if (getBlockValues().containsKey(md)) {
return getBlockValues().get(md);
public Integer getValue(World world, Object obj) {
if (obj instanceof EntityType et) {
// Check world settings
if (getWorldSpawnerValues().containsKey(world) && getWorldSpawnerValues().get(world).containsKey(et)) {
return getWorldSpawnerValues().get(world).get(et);
}
// Check baseline
if (getSpawnerValues().containsKey(et)) {
return getSpawnerValues().get(et);
}
} else if (obj instanceof Material md) {
// Check world settings
if (getWorldBlockValues().containsKey(world) && getWorldBlockValues().get(world).containsKey(md)) {
return getWorldBlockValues().get(world).get(md);
}
// Check baseline
if (getBlockValues().containsKey(md)) {
return getBlockValues().get(md);
}
}
return null;
}
/**
* Return true if the block should be hidden
* @param m block material
* @param m block material or entity type of spanwer
* @return true if hidden
*/
public boolean isHiddenBlock(Material m) {
return hiddenBlocks.contains(m);
public boolean isHiddenBlock(Object obj) {
if (obj instanceof Material m) {
return hiddenBlocks.contains(m);
}
return hiddenBlocks.contains(Material.SPAWNER);
}
/**
* Return true if the block should not be hidden
* @param m block material
* @return false if hidden
*/
public boolean isNotHiddenBlock(Material m) {
return !hiddenBlocks.contains(m);
public boolean isNotHiddenBlock(Object obj) {
if (obj instanceof Material m) {
return !hiddenBlocks.contains(m);
} else {
return !hiddenBlocks.contains(Material.SPAWNER);
}
}
/**
* @return the spawnerValues
*/
public Map<EntityType, Integer> getSpawnerValues() {
return spawnerValues;
}
/**
* @param spawnerValues the spawnerValues to set
*/
public void setSpawnerValues(Map<EntityType, Integer> spawnerValues) {
this.spawnerValues = spawnerValues;
}
}

View File

@ -26,8 +26,6 @@ public class MigrationListener implements Listener
@EventHandler
public void onBentoBoxReady(BentoBoxReadyEvent e) {
// Perform upgrade check
this.addon.getManager().migrate();
// Load TopTens
this.addon.getManager().loadTopTens();
/*

View File

@ -1,9 +1,11 @@
package world.bentobox.level.objects;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import org.bukkit.Material;
import org.bukkit.entity.EntityType;
import com.google.gson.annotations.Expose;
@ -59,13 +61,13 @@ public class IslandLevels implements DataObject {
* Underwater count
*/
@Expose
private Map<Material, Integer> uwCount;
private Map<Object, Integer> uwCount;
/**
* MaterialData count - count of all blocks excluding under water
*/
@Expose
private Map<Material, Integer> mdCount;
private Map<Object, Integer> mdCount;
/**
* Constructor for new island
@ -73,8 +75,8 @@ public class IslandLevels implements DataObject {
*/
public IslandLevels(String islandUUID) {
uniqueId = islandUUID;
uwCount = new EnumMap<>(Material.class);
mdCount = new EnumMap<>(Material.class);
uwCount = new HashMap<>();
mdCount = new HashMap<>();
}
/**
@ -165,31 +167,67 @@ public class IslandLevels implements DataObject {
* The count of underwater blocks
* @return the uwCount
*/
public Map<Material, Integer> getUwCount() {
public Map<Object, Integer> getUwCount() {
return uwCount;
}
/**
* Underwater blocks
* @param uwCount the uwCount to set
* @param map the uwCount to set
*/
public void setUwCount(Map<Material, Integer> uwCount) {
this.uwCount = uwCount;
public void setUwCount(Map<Object, Integer> map) {
// Loaded objects come in as strings, so need to be converted to Material Or EntityTypes
uwCount = convertMap(uwCount);
this.uwCount = map;
}
/**
* All blocks count except for underwater blocks
* @return the mdCount
*/
public Map<Material, Integer> getMdCount() {
public Map<Object, Integer> getMdCount() {
// Loaded objects come in as strings, so need to be converted to Material Or EntityTypes
mdCount = convertMap(mdCount);
return mdCount;
}
private Map<Object, Integer> convertMap(Map<Object, Integer> mdCount) {
Map<Object, Integer> convertedMap = new HashMap<>();
for (Map.Entry<Object, Integer> entry : mdCount.entrySet()) {
Object key = entry.getKey();
Integer value = entry.getValue();
if (key instanceof String) {
String keyStr = (String) key;
// First, try converting to Material
Material material = Material.matchMaterial(keyStr);
if (material != null) {
convertedMap.put(material, value);
} else {
// Fallback to converting to EntityType (using uppercase as enum constants are uppercase)
try {
EntityType entityType = EntityType.valueOf(keyStr.toUpperCase(Locale.ENGLISH));
convertedMap.put(entityType, value);
} catch (IllegalArgumentException ex) {
// No valid Material or EntityType found.
convertedMap.put(key, value); // Leave the key unchanged.
}
}
} else {
// If the key is not a String, add it directly.
convertedMap.put(key, value);
}
}
return convertedMap;
}
/**
* All blocks except for underwater blocks
* @param mdCount the mdCount to set
*/
public void setMdCount(Map<Material, Integer> mdCount) {
public void setMdCount(Map<Object, Integer> mdCount) {
this.mdCount = mdCount;
}

View File

@ -4,12 +4,16 @@ import java.io.File;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.entity.EntityType;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.inventory.ItemStack;
@ -24,7 +28,6 @@ import world.bentobox.bentobox.api.panels.builders.TemplatedPanelBuilder;
import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.util.Pair;
import world.bentobox.level.Level;
import world.bentobox.level.objects.IslandLevels;
import world.bentobox.level.util.Utils;
@ -33,10 +36,112 @@ import world.bentobox.level.util.Utils;
* This class opens GUI that shows generator view for user.
*/
public class DetailsPanel {
private static final String SPAWNER = "_SPAWNER";
// ---------------------------------------------------------------------
// Section: Internal Constructor
// Section: Enums
// ---------------------------------------------------------------------
/**
* This enum holds possible tabs for current gui.
*/
private enum Tab {
/**
* All block Tab
*/
ALL_BLOCKS,
/**
* Blocks that have value
*/
VALUE_BLOCKS,
/**
* Above Sea level Tab.
*/
ABOVE_SEA_LEVEL,
/**
* Underwater Tab.
*/
UNDERWATER,
/**
* Spawner Tab.
*/
SPAWNER
}
/**
* Sorting order of blocks.
*/
private enum Filter {
/**
* By name
*/
NAME,
/**
* By value
*/
VALUE,
/**
* By number
*/
COUNT
}
// ---------------------------------------------------------------------
// Section: Variables
// ---------------------------------------------------------------------
/**
* This variable holds targeted island.
*/
private final Island island;
/**
* This variable holds targeted island level data.
*/
private final IslandLevels levelsData;
/**
* This variable allows to access addon object.
*/
private final Level addon;
/**
* This variable holds user who opens panel. Without it panel cannot be opened.
*/
private final User user;
/**
* This variable holds a world to which gui referee.
*/
private final World world;
/**
* Record that stores a Material or EntityType as a key and a value
*/
private record BlockRec(Object key, Integer value, Integer limit) {
}
/**
* This variable stores the list of elements to display.
*/
private final List<BlockRec> blockCountList;
/**
* This variable holds current pageIndex for multi-page generator choosing.
*/
private int pageIndex;
/**
* This variable stores which tab currently is active.
*/
private Tab activeTab;
/**
* This variable stores active filter for items.
*/
private Filter activeFilter;
/**
* This is internal constructor. It is used internally in current class to avoid
* creating objects everywhere.
@ -58,10 +163,10 @@ public class DetailsPanel {
this.levelsData = null;
}
// By default no-filters are active.
// Default Filters
this.activeTab = Tab.VALUE_BLOCKS;
this.activeFilter = Filter.NAME;
this.materialCountList = new ArrayList<>(Material.values().length);
this.blockCountList = new ArrayList<>();
this.updateFilters();
}
@ -93,7 +198,7 @@ public class DetailsPanel {
panelBuilder.registerTypeBuilder("NEXT", this::createNextButton);
panelBuilder.registerTypeBuilder("PREVIOUS", this::createPreviousButton);
panelBuilder.registerTypeBuilder("BLOCK", this::createMaterialButton);
panelBuilder.registerTypeBuilder("BLOCK", this::createBlockButton);
panelBuilder.registerTypeBuilder("FILTER", this::createFilterButton);
@ -108,101 +213,81 @@ public class DetailsPanel {
* This method updates filter of elements based on tabs.
*/
private void updateFilters() {
this.materialCountList.clear();
this.blockCountList.clear();
switch (this.activeTab) {
case VALUE_BLOCKS -> {
Map<Material, Integer> materialCountMap = new EnumMap<>(Material.class);
materialCountMap.putAll(this.levelsData.getMdCount());
// Add underwater blocks.
this.levelsData.getUwCount().forEach((material, count) -> materialCountMap.put(material,
materialCountMap.computeIfAbsent(material, key -> 0) + count));
// Remove zero value blocks
materialCountMap.entrySet().removeIf(en -> {
Integer value = this.addon.getBlockConfig().getValue(world, en.getKey());
return value == null || value == 0;
});
// Remove any hidden blocks
materialCountMap.keySet().removeIf(this.addon.getBlockConfig()::isHiddenBlock);
materialCountMap.entrySet().stream().sorted((Map.Entry.comparingByKey())).forEachOrdered(entry -> {
if (entry.getValue() > 0) {
this.materialCountList.add(new Pair<>(entry.getKey(), entry.getValue()));
}
});
}
case ALL_BLOCKS -> {
Map<Material, Integer> materialCountMap = new EnumMap<>(Material.class);
materialCountMap.putAll(this.levelsData.getMdCount());
// Add underwater blocks.
this.levelsData.getUwCount().forEach((material, count) -> materialCountMap.put(material,
materialCountMap.computeIfAbsent(material, key -> 0) + count));
// Remove any hidden blocks
materialCountMap.keySet().removeIf(this.addon.getBlockConfig()::isHiddenBlock);
materialCountMap.entrySet().stream().sorted((Map.Entry.comparingByKey()))
.forEachOrdered(entry -> this.materialCountList.add(new Pair<>(entry.getKey(), entry.getValue())));
}
case ABOVE_SEA_LEVEL -> this.levelsData.getMdCount().entrySet().stream()
.filter(en -> this.addon.getBlockConfig().isNotHiddenBlock(en.getKey()))
.sorted((Map.Entry.comparingByKey()))
.forEachOrdered(entry -> this.materialCountList.add(new Pair<>(entry.getKey(), entry.getValue())));
case UNDERWATER -> this.levelsData.getUwCount().entrySet().stream()
.filter(en -> this.addon.getBlockConfig().isNotHiddenBlock(en.getKey()))
.sorted((Map.Entry.comparingByKey()))
.forEachOrdered(entry -> this.materialCountList.add(new Pair<>(entry.getKey(), entry.getValue())));
case SPAWNER -> {
if (this.activeTab == Tab.SPAWNER) {
if (this.addon.getBlockConfig().isNotHiddenBlock(Material.SPAWNER)) {
int aboveWater = this.levelsData.getMdCount().getOrDefault(Material.SPAWNER, 0);
int underWater = this.levelsData.getUwCount().getOrDefault(Material.SPAWNER, 0);
Map<EntityType, Integer> spawnerCountMap = new EnumMap<>(EntityType.class);
spawnerCountMap = this.levelsData.getMdCount().entrySet().stream()
.filter(en -> en.getKey() instanceof EntityType)
.collect(Collectors.toMap(en -> (EntityType) en.getKey(), Map.Entry::getValue));
// TODO: spawners need some touch...
this.materialCountList.add(new Pair<>(Material.SPAWNER, underWater + aboveWater));
spawnerCountMap.entrySet().stream().sorted((Map.Entry.comparingByKey())).forEachOrdered(entry -> {
if (entry.getValue() > 0) {
this.blockCountList.add(new BlockRec(entry.getKey(), entry.getValue(), 0));
}
});
}
}
}
} else {
Map<Object, Integer> materialCountMap = new HashMap<>();
Comparator<Pair<Material, Integer>> sorter;
if (this.activeTab != Tab.UNDERWATER) {
// All above water blocks
materialCountMap.putAll(this.levelsData.getMdCount());
}
if (this.activeTab != Tab.ABOVE_SEA_LEVEL) {
// All underwater blocks.
materialCountMap.putAll(this.levelsData.getUwCount());
}
// Remove any hidden blocks
materialCountMap.keySet().removeIf(this.addon.getBlockConfig()::isHiddenBlock);
// Remove any zero amount items
materialCountMap.values().removeIf(i -> i < 1);
if (this.activeTab == Tab.VALUE_BLOCKS) {
// Remove zero-value blocks
materialCountMap.entrySet().removeIf(en -> Optional
.ofNullable(this.addon.getBlockConfig().getValue(world, en.getKey())).orElse(0) == 0);
}
// All done filtering, add the left overs
blockCountList.addAll(
materialCountMap.entrySet().stream()
.map(entry -> new BlockRec(entry.getKey(), entry.getValue(), 0))
.collect(Collectors.toList()));
}
// Sort and filter
Comparator<BlockRec> sorter;
switch (this.activeFilter) {
case COUNT -> {
sorter = (o1, o2) -> {
if (o1.getValue().equals(o2.getValue())) {
String o1Name = Utils.prettifyObject(o1.getKey(), this.user);
String o2Name = Utils.prettifyObject(o2.getKey(), this.user);
if (o1.value().equals(o2.value())) {
String o1Name = Utils.prettifyObject(o1.key(), this.user);
String o2Name = Utils.prettifyObject(o2.key(), this.user);
return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name);
} else {
return Integer.compare(o2.getValue(), o1.getValue());
return Integer.compare(o2.value(), o1.value());
}
};
}
case VALUE -> {
sorter = (o1, o2) -> {
int blockLimit = this.addon.getBlockConfig().getBlockLimits().getOrDefault(o1.getKey(), 0);
int o1Count = blockLimit > 0 ? Math.min(o1.getValue(), blockLimit) : o1.getValue();
int blockLimit = Objects.requireNonNullElse(this.addon.getBlockConfig().getLimit(o1.key()), 0);
int o1Count = blockLimit > 0 ? Math.min(o1.value(), blockLimit) : o1.value();
blockLimit = this.addon.getBlockConfig().getBlockLimits().getOrDefault(o2.getKey(), 0);
int o2Count = blockLimit > 0 ? Math.min(o2.getValue(), blockLimit) : o2.getValue();
blockLimit = Objects.requireNonNullElse(this.addon.getBlockConfig().getLimit(o2.key()), 0);
int o2Count = blockLimit > 0 ? Math.min(o2.value(), blockLimit) : o2.value();
long o1Value = (long) o1Count
* this.addon.getBlockConfig().getBlockValues().getOrDefault(o1.getKey(), 0);
* this.addon.getBlockConfig().getBlockValues().getOrDefault(o1.key(), 0);
long o2Value = (long) o2Count
* this.addon.getBlockConfig().getBlockValues().getOrDefault(o2.getKey(), 0);
* this.addon.getBlockConfig().getBlockValues().getOrDefault(o2.key(), 0);
if (o1Value == o2Value) {
String o1Name = Utils.prettifyObject(o1.getKey(), this.user);
String o2Name = Utils.prettifyObject(o2.getKey(), this.user);
String o1Name = Utils.prettifyObject(o1.key(), this.user);
String o2Name = Utils.prettifyObject(o2.key(), this.user);
return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name);
} else {
@ -212,16 +297,15 @@ public class DetailsPanel {
}
default -> {
sorter = (o1, o2) -> {
String o1Name = Utils.prettifyObject(o1.getKey(), this.user);
String o2Name = Utils.prettifyObject(o2.getKey(), this.user);
String o1Name = Utils.prettifyObject(o1.key(), this.user);
String o2Name = Utils.prettifyObject(o2.key(), this.user);
return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name);
};
}
}
this.materialCountList.sort(sorter);
this.blockCountList.sort(sorter);
this.pageIndex = 0;
}
@ -397,7 +481,7 @@ public class DetailsPanel {
* @return the panel item
*/
private PanelItem createNextButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
long size = this.materialCountList.size();
long size = this.blockCountList.size();
if (size <= slot.amountMap().getOrDefault("BLOCK", 1)
|| 1.0 * size / slot.amountMap().getOrDefault("BLOCK", 1) <= this.pageIndex + 1) {
@ -533,73 +617,91 @@ public class DetailsPanel {
* @param slot the slot
* @return the panel item
*/
private PanelItem createMaterialButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
if (this.materialCountList.isEmpty()) {
// Does not contain any generators.
private PanelItem createBlockButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) {
if (this.blockCountList.isEmpty()) {
// Nothing to show
return null;
}
int index = this.pageIndex * slot.amountMap().getOrDefault("BLOCK", 1) + slot.slot();
if (index >= this.materialCountList.size()) {
if (index >= this.blockCountList.size()) {
// Out of index.
return null;
}
return this.createMaterialButton(template, this.materialCountList.get(index));
return this.createMaterialButton(template, this.blockCountList.get(index));
}
/**
* This method creates button for material.
* This method creates button for block.
*
* @param template the template of the button
* @param materialCount materialCount which button must be created.
* @return PanelItem for generator tier.
* @param blockCount count
* @return PanelItem button
*/
private PanelItem createMaterialButton(ItemTemplateRecord template, Pair<Material, Integer> materialCount) {
private PanelItem createMaterialButton(ItemTemplateRecord template, BlockRec blockCount) {
PanelItemBuilder builder = new PanelItemBuilder();
if (template.icon() != null) {
builder.icon(template.icon().clone());
} else {
builder.icon(PanelUtils.getMaterialItem(materialCount.getKey()));
}
// Show amount if less than 64.
if (blockCount.value() < 64) {
builder.amount(blockCount.value());
}
if (materialCount.getValue() < 64) {
builder.amount(materialCount.getValue());
final String reference = "level.gui.buttons.material.";
Object key = blockCount.key();
String description = Utils.prettifyDescription(key, this.user);
String blockId = "";
int blockValue = 0;
int blockLimit = 0;
String value = "";
String limit = "";
String displayMaterial = Utils.prettifyObject(key, this.user);
if (key instanceof Material m) {
// Material-specific settings.
builder.icon(PanelUtils.getMaterialItem(m));
blockId = this.user.getTranslationOrNothing(reference + "id", "[id]", m.name());
blockValue = this.addon.getBlockConfig().getBlockValues().getOrDefault(m, 0);
blockLimit = Objects.requireNonNullElse(this.addon.getBlockConfig().getLimit(m), 0);
} else if (key instanceof EntityType e) {
// EntityType-specific settings.
builder.icon(PanelUtils.getEntityEgg(e));
description += this.user.getTranslation(this.world, "level.gui.buttons.spawner.block-name"); // Put spawner on the end
blockId = this.user.getTranslationOrNothing(reference + "id", "[id]", e.name().concat(SPAWNER));
blockValue = this.addon.getBlockConfig().getSpawnerValues().getOrDefault(e, 0);
blockLimit = Objects.requireNonNullElse(this.addon.getBlockConfig().getLimit(e), 0);
}
if (template.title() != null) {
builder.name(this.user.getTranslation(this.world, template.title(), TextVariables.NUMBER,
String.valueOf(materialCount.getValue()), "[material]",
Utils.prettifyObject(materialCount.getKey(), this.user)));
String.valueOf(blockCount.value()), "[material]", displayMaterial));
}
String description = Utils.prettifyDescription(materialCount.getKey(), this.user);
final String reference = "level.gui.buttons.material.";
String blockId = this.user.getTranslationOrNothing(reference + "id", "[id]", materialCount.getKey().name());
int blockValue = this.addon.getBlockConfig().getBlockValues().getOrDefault(materialCount.getKey(), 0);
String value = blockValue > 0
value = blockValue > 0
? this.user.getTranslationOrNothing(reference + "value", TextVariables.NUMBER,
String.valueOf(blockValue))
: "";
int blockLimit = this.addon.getBlockConfig().getBlockLimits().getOrDefault(materialCount.getKey(), 0);
String limit = blockLimit > 0
: "";
limit = blockLimit > 0
? this.user.getTranslationOrNothing(reference + "limit", TextVariables.NUMBER,
String.valueOf(blockLimit))
: "";
: "";
String count = this.user.getTranslationOrNothing(reference + "count", TextVariables.NUMBER,
String.valueOf(materialCount.getValue()));
String.valueOf(blockCount.value()));
long calculatedValue = (long) Math.min(blockLimit > 0 ? blockLimit : Integer.MAX_VALUE,
materialCount.getValue()) * blockValue;
blockCount.value()) * blockValue;
String valueText = calculatedValue > 0 ? this.user.getTranslationOrNothing(reference + "calculated",
TextVariables.NUMBER, String.valueOf(calculatedValue)) : "";
// Hide block ID unless user is an operator.
if (!user.isOp()) {
blockId = "";
}
if (template.description() != null) {
builder.description(this.user
.getTranslation(this.world, template.description(), "[description]", description, "[id]", blockId,
@ -626,100 +728,5 @@ public class DetailsPanel {
new DetailsPanel(addon, world, user).build();
}
// ---------------------------------------------------------------------
// Section: Enums
// ---------------------------------------------------------------------
/**
* This enum holds possible tabs for current gui.
*/
private enum Tab {
/**
* All block Tab
*/
ALL_BLOCKS,
/**
* Blocks that have value
*/
VALUE_BLOCKS,
/**
* Above Sea level Tab.
*/
ABOVE_SEA_LEVEL,
/**
* Underwater Tab.
*/
UNDERWATER,
/**
* Spawner Tab.
*/
SPAWNER
}
/**
* Sorting order of blocks.
*/
private enum Filter {
/**
* By name
*/
NAME,
/**
* By value
*/
VALUE,
/**
* By number
*/
COUNT
}
// ---------------------------------------------------------------------
// Section: Variables
// ---------------------------------------------------------------------
/**
* This variable holds targeted island.
*/
private final Island island;
/**
* This variable holds targeted island level data.
*/
private final IslandLevels levelsData;
/**
* This variable allows to access addon object.
*/
private final Level addon;
/**
* This variable holds user who opens panel. Without it panel cannot be opened.
*/
private final User user;
/**
* This variable holds a world to which gui referee.
*/
private final World world;
/**
* This variable stores the list of elements to display.
*/
private final List<Pair<Material, Integer>> materialCountList;
/**
* This variable holds current pageIndex for multi-page generator choosing.
*/
private int pageIndex;
/**
* This variable stores which tab currently is active.
*/
private Tab activeTab;
/**
* This variable stores active filter for items.
*/
private Filter activeFilter;
}

View File

@ -6,11 +6,13 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.entity.EntityType;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.inventory.ItemStack;
@ -34,11 +36,86 @@ import world.bentobox.level.util.Utils;
*/
public class ValuePanel
{
// ---------------------------------------------------------------------
// Section: Internal Constructor
// Section: Enums
// ---------------------------------------------------------------------
/**
* Sorting order of blocks.
*/
private enum Filter {
/**
* By name asc
*/
NAME_ASC,
/**
* By name desc
*/
NAME_DESC,
/**
* By value asc
*/
VALUE_ASC,
/**
* By value desc
*/
VALUE_DESC,
}
private record BlockRecord(Object keyl, Integer value, Integer limit) {
}
// ---------------------------------------------------------------------
// Section: Constants
// ---------------------------------------------------------------------
private static final String BLOCK = "BLOCK";
private static final String SPAWNER = "_SPAWNER";
// ---------------------------------------------------------------------
// Section: Variables
// ---------------------------------------------------------------------
/**
* This variable allows to access addon object.
*/
private final Level addon;
/**
* This variable holds user who opens panel. Without it panel cannot be opened.
*/
private final User user;
/**
* This variable holds a world to which gui referee.
*/
private final World world;
/**
* This variable stores the list of elements to display.
*/
private final List<BlockRecord> blockRecordList;
/**
* This variable stores the list of elements to display.
*/
private List<BlockRecord> elementList;
/**
* This variable holds current pageIndex for multi-page generator choosing.
*/
private int pageIndex;
/**
* This variable stores which tab currently is active.
*/
private String searchText;
/**
* This variable stores active filter for items.
*/
private Filter activeFilter;
/**
* This is internal constructor. It is used internally in current class to avoid creating objects everywhere.
@ -56,7 +133,8 @@ public class ValuePanel
this.user = user;
this.activeFilter = Filter.NAME_ASC;
this.materialRecordList = Arrays.stream(Material.values()).
this.blockRecordList = Arrays.stream(Material.values()).
filter(Material::isBlock).
filter(Material::isItem). // Remove things like PITCHER_CROP
filter(m -> !m.name().startsWith("LEGACY_")).
@ -64,13 +142,22 @@ public class ValuePanel
map(material ->
{
Integer value = this.addon.getBlockConfig().getValue(this.world, material);
Integer limit = this.addon.getBlockConfig().getBlockLimits().get(material);
Integer limit = this.addon.getBlockConfig().getLimit(material);
return new MaterialRecord(material,
return new BlockRecord(material,
value != null ? value : 0,
limit != null ? limit : 0);
}).
collect(Collectors.toList());
// Add spawn eggs
this.blockRecordList.addAll(Arrays.stream(EntityType.values()).filter(EntityType::isSpawnable)
.filter(EntityType::isAlive)
.filter(this.addon.getBlockConfig()::isNotHiddenBlock).map(et -> {
Integer value = this.addon.getBlockConfig().getValue(this.world, et);
Integer limit = this.addon.getBlockConfig().getLimit(et);
return new BlockRecord(et, value != null ? value : 0, limit != null ? limit : 0);
}).collect(Collectors.toList()));
this.elementList = new ArrayList<>(Material.values().length);
this.searchText = "";
@ -108,7 +195,7 @@ public class ValuePanel
*/
private void updateFilters()
{
Comparator<MaterialRecord> sorter;
Comparator<BlockRecord> sorter;
switch (this.activeFilter)
{
@ -118,8 +205,8 @@ public class ValuePanel
{
if (o1.value().equals(o2.value()))
{
String o1Name = Utils.prettifyObject(o1.material(), this.user);
String o2Name = Utils.prettifyObject(o2.material(), this.user);
String o1Name = Utils.prettifyObject(o1.keyl(), this.user);
String o2Name = Utils.prettifyObject(o2.keyl(), this.user);
return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name);
}
@ -135,8 +222,8 @@ public class ValuePanel
{
if (o1.value().equals(o2.value()))
{
String o1Name = Utils.prettifyObject(o1.material(), this.user);
String o2Name = Utils.prettifyObject(o2.material(), this.user);
String o1Name = Utils.prettifyObject(o1.keyl(), this.user);
String o2Name = Utils.prettifyObject(o2.keyl(), this.user);
return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name);
}
@ -150,8 +237,8 @@ public class ValuePanel
sorter = (o1, o2) ->
{
String o1Name = Utils.prettifyObject(o1.material(), this.user);
String o2Name = Utils.prettifyObject(o2.material(), this.user);
String o1Name = Utils.prettifyObject(o1.keyl(), this.user);
String o2Name = Utils.prettifyObject(o2.keyl(), this.user);
return String.CASE_INSENSITIVE_ORDER.compare(o2Name, o1Name);
};
@ -160,25 +247,26 @@ public class ValuePanel
sorter = (o1, o2) ->
{
String o1Name = Utils.prettifyObject(o1.material(), this.user);
String o2Name = Utils.prettifyObject(o2.material(), this.user);
String o1Name = Utils.prettifyObject(o1.keyl(), this.user);
String o2Name = Utils.prettifyObject(o2.keyl(), this.user);
return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name);
};
}
this.materialRecordList.sort(sorter);
this.blockRecordList.sort(sorter);
if (!this.searchText.isBlank())
{
this.elementList = new ArrayList<>(this.materialRecordList.size());
this.elementList = new ArrayList<>(this.blockRecordList.size());
final String text = this.searchText.toLowerCase();
this.materialRecordList.forEach(rec ->
this.blockRecordList.forEach(rec ->
{
if (rec.material.name().toLowerCase().contains(text) ||
Utils.prettifyObject(rec.material(), this.user).toLowerCase().contains(text))
if (rec.keyl.toString().toLowerCase().contains(text)
||
Utils.prettifyObject(rec.keyl(), this.user).toLowerCase().contains(text))
{
this.elementList.add(rec);
}
@ -186,7 +274,7 @@ public class ValuePanel
}
else
{
this.elementList = this.materialRecordList;
this.elementList = this.blockRecordList;
}
this.pageIndex = 0;
@ -585,7 +673,6 @@ public class ValuePanel
return null;
}
@SuppressWarnings("deprecation")
int index = this.pageIndex * slot.amountMap().getOrDefault(BLOCK, 1) + slot.slot();
if (index >= this.elementList.size())
@ -597,6 +684,84 @@ public class ValuePanel
return this.createMaterialButton(template, this.elementList.get(index));
}
/**
* This method creates button for block.
*
* @param template the template of the button
* @param blockCount count
* @return PanelItem button
*/
private PanelItem createMaterialButton(ItemTemplateRecord template, BlockRecord blockCount) {
PanelItemBuilder builder = new PanelItemBuilder();
if (template.icon() != null) {
builder.icon(template.icon().clone());
}
// Show amount if less than 64.
if (blockCount.value() < 64) {
builder.amount(blockCount.value());
}
final String reference = "level.gui.buttons.material.";
Object key = blockCount.keyl();
String description = Utils.prettifyDescription(key, this.user);
String blockId = "";
int blockValue = 0;
int blockLimit = 0;
String value = "";
String limit = "";
String displayMaterial = Utils.prettifyObject(key, this.user);
if (key instanceof Material m) {
// Material-specific settings.
builder.icon(PanelUtils.getMaterialItem(m));
blockId = this.user.getTranslationOrNothing(reference + "id", "[id]", m.name());
blockValue = this.addon.getBlockConfig().getBlockValues().getOrDefault(m, 0);
blockLimit = Objects.requireNonNullElse(this.addon.getBlockConfig().getLimit(m), 0);
} else if (key instanceof EntityType e) {
// EntityType-specific settings.
builder.icon(PanelUtils.getEntityEgg(e));
description += this.user.getTranslation(this.world, "level.gui.buttons.spawner.block-name"); // Put spawner on the end
blockId = this.user.getTranslationOrNothing(reference + "id", "[id]", e.name().concat(SPAWNER));
blockValue = this.addon.getBlockConfig().getSpawnerValues().getOrDefault(e, 0);
blockLimit = Objects.requireNonNullElse(this.addon.getBlockConfig().getLimit(e), 0);
}
if (template.title() != null) {
builder.name(this.user.getTranslation(this.world, template.title(), TextVariables.NUMBER,
String.valueOf(blockCount.value()), "[material]", displayMaterial));
}
value = blockValue > 0
? this.user.getTranslationOrNothing(reference + "value", TextVariables.NUMBER,
String.valueOf(blockValue))
: "";
limit = blockLimit > 0
? this.user.getTranslationOrNothing(reference + "limit", TextVariables.NUMBER,
String.valueOf(blockLimit))
: "";
// Hide block ID unless user is an operator.
if (!user.isOp()) {
blockId = "";
}
String underWater;
if (this.addon.getSettings().getUnderWaterMultiplier() > 1.0) {
underWater = this.user.getTranslationOrNothing(reference + "underwater", TextVariables.NUMBER,
String.valueOf(blockCount.value() * this.addon.getSettings().getUnderWaterMultiplier()));
} else {
underWater = "";
}
if (template.description() != null) {
builder.description(this.user
.getTranslation(this.world, template.description(), "[description]", description, "[id]", blockId,
"[value]", value, "[underwater]", underWater, "[limit]", limit)
.replaceAll("(?m)^[ \\t]*\\r?\\n", "").replaceAll("(?<!\\\\)\\|", "\n").replace("\\\\\\|", "|")); // Non regex
}
return builder.build();
}
/**
* This method creates button for material.
@ -605,11 +770,12 @@ public class ValuePanel
* @param materialRecord materialRecord which button must be created.
* @return PanelItem for generator tier.
*/
/*
private PanelItem createMaterialButton(ItemTemplateRecord template,
MaterialRecord materialRecord)
{
PanelItemBuilder builder = new PanelItemBuilder();
if (template.icon() != null)
{
builder.icon(template.icon().clone());
@ -618,29 +784,29 @@ public class ValuePanel
{
builder.icon(PanelUtils.getMaterialItem(materialRecord.material()));
}
if (materialRecord.value() <= 64 && materialRecord.value() > 0)
{
builder.amount(materialRecord.value());
}
if (template.title() != null)
{
builder.name(this.user.getTranslation(this.world, template.title(),
"[material]", Utils.prettifyObject(materialRecord.material(), this.user)));
}
String description = Utils.prettifyDescription(materialRecord.material(), this.user);
final String reference = "level.gui.buttons.material.";
String blockId = this.user.getTranslationOrNothing(reference + "id",
"[id]", materialRecord.material().name());
String value = this.user.getTranslationOrNothing(reference + "value",
TextVariables.NUMBER, String.valueOf(materialRecord.value()));
String underWater;
if (this.addon.getSettings().getUnderWaterMultiplier() > 1.0)
{
underWater = this.user.getTranslationOrNothing(reference + "underwater",
@ -650,10 +816,10 @@ public class ValuePanel
{
underWater = "";
}
String limit = materialRecord.limit() > 0 ? this.user.getTranslationOrNothing(reference + "limit",
TextVariables.NUMBER, String.valueOf(materialRecord.limit())) : "";
if (template.description() != null)
{
builder.description(this.user.getTranslation(this.world, template.description(),
@ -666,14 +832,14 @@ public class ValuePanel
replaceAll("(?<!\\\\)\\|", "\n").
replace("\\\\\\|", "|")); // Non regex
}
builder.clickHandler((panel, user1, clickType, i) -> {
addon.log("Material: " + materialRecord.material());
return true;
});
return builder.build();
}
}*/
// ---------------------------------------------------------------------
@ -696,87 +862,4 @@ public class ValuePanel
new ValuePanel(addon, world, user).build();
}
// ---------------------------------------------------------------------
// Section: Enums
// ---------------------------------------------------------------------
/**
* Sorting order of blocks.
*/
private enum Filter
{
/**
* By name asc
*/
NAME_ASC,
/**
* By name desc
*/
NAME_DESC,
/**
* By value asc
*/
VALUE_ASC,
/**
* By value desc
*/
VALUE_DESC,
}
private record MaterialRecord(Material material, Integer value, Integer limit)
{
}
// ---------------------------------------------------------------------
// Section: Constants
// ---------------------------------------------------------------------
private static final String BLOCK = "BLOCK";
// ---------------------------------------------------------------------
// Section: Variables
// ---------------------------------------------------------------------
/**
* This variable allows to access addon object.
*/
private final Level addon;
/**
* This variable holds user who opens panel. Without it panel cannot be opened.
*/
private final User user;
/**
* This variable holds a world to which gui referee.
*/
private final World world;
/**
* This variable stores the list of elements to display.
*/
private final List<MaterialRecord> materialRecordList;
/**
* This variable stores the list of elements to display.
*/
private List<MaterialRecord> elementList;
/**
* This variable holds current pageIndex for multi-page generator choosing.
*/
private int pageIndex;
/**
* This variable stores which tab currently is active.
*/
private String searchText;
/**
* This variable stores active filter for items.
*/
private Filter activeFilter;
}

View File

@ -10,6 +10,7 @@ package world.bentobox.level.util;
import java.util.List;
import org.bukkit.Material;
import org.bukkit.entity.EntityType;
import org.bukkit.permissions.PermissionAttachmentInfo;
import world.bentobox.bentobox.api.user.User;
@ -18,7 +19,6 @@ import world.bentobox.bentobox.hooks.LangUtilsHook;
public class Utils
{
private static final String LEVEL_MATERIALS = "level.materials.";
private Utils() {} // Private constructor as this is a utility class only with static methods
@ -138,92 +138,72 @@ public class Utils
return currentValue;
}
/**
* Prettify Material object for user.
* @param object Object that must be pretty.
* @param user User who will see the object.
* @return Prettified string for Material.
* Reference string in translations.
*/
public static String prettifyObject(Material object, User user)
{
// Nothing to translate
if (object == null)
{
public static final String ENTITIES = "level.entities.";
private static final String LEVEL_MATERIALS = "level.materials.";
public static String prettifyObject(Object object, User user) {
if (object == null || !(object instanceof Enum<?>)) {
return "";
}
// All supported objects are enums so we can use name() safely.
String key = ((Enum<?>) object).name().toLowerCase();
String translation = "";
// Find addon structure with:
// [addon]:
// materials:
// [material]:
// name: [name]
String translation = user.getTranslationOrNothing(LEVEL_MATERIALS + object.name().toLowerCase() + ".name");
if (object instanceof Material) {
// Try our translations for Material.
translation = user.getTranslationOrNothing(LEVEL_MATERIALS + key + ".name");
if (!translation.isEmpty())
return translation;
if (!translation.isEmpty())
{
// We found our translation.
return translation;
translation = user.getTranslationOrNothing(LEVEL_MATERIALS + key);
if (!translation.isEmpty())
return translation;
translation = user.getTranslationOrNothing("materials." + key);
if (!translation.isEmpty())
return translation;
// Fallback to our hook for Material.
return LangUtilsHook.getMaterialName((Material) object, user);
} else if (object instanceof EntityType) {
// Try our translations for EntityType.
translation = user.getTranslationOrNothing(ENTITIES + key + ".name");
if (!translation.isEmpty())
return translation;
translation = user.getTranslationOrNothing(ENTITIES + key);
if (!translation.isEmpty())
return translation;
translation = user.getTranslationOrNothing("entities." + key);
if (!translation.isEmpty())
return translation;
// Fallback to our hook for EntityType.
return LangUtilsHook.getEntityName((EntityType) object, user);
}
// Find addon structure with:
// [addon]:
// materials:
// [material]: [name]
translation = user.getTranslationOrNothing(LEVEL_MATERIALS + object.name().toLowerCase());
if (!translation.isEmpty())
{
// We found our translation.
return translation;
}
// Find general structure with:
// materials:
// [material]: [name]
translation = user.getTranslationOrNothing("materials." + object.name().toLowerCase());
if (!translation.isEmpty())
{
// We found our translation.
return translation;
}
// Use Lang Utils Hook to translate material
return LangUtilsHook.getMaterialName(object, user);
// In case of an unexpected type, return an empty string.
return "";
}
/**
* Prettify Material object description for user.
* @param object Object that must be pretty.
* @param user User who will see the object.
* @return Prettified description string for Material.
*/
public static String prettifyDescription(Material object, User user)
{
// Nothing to translate
if (object == null)
{
public static String prettifyDescription(Object object, User user) {
if (object == null || !(object instanceof Enum<?>)) {
return "";
}
String key = ((Enum<?>) object).name().toLowerCase();
// Find addon structure with:
// [addon]:
// materials:
// [material]:
// description: [text]
String translation = user.getTranslationOrNothing(LEVEL_MATERIALS + object.name().toLowerCase() + ".description");
if (!translation.isEmpty())
{
// We found our translation.
return translation;
if (object instanceof Material) {
String translation = user.getTranslationOrNothing(LEVEL_MATERIALS + key + ".description");
return translation != null ? translation : "";
} else if (object instanceof EntityType) {
String translation = user.getTranslationOrNothing(ENTITIES + key + ".description");
return translation != null ? translation : "";
}
// No text to return.
return "";
}
}

View File

@ -142,6 +142,7 @@ level:
name: "&f&l Spawners"
description: |-
&7 Display only spawners.
block-name: "&b Spawner"
filters:
name:
name: "&f&l Sort by Name"

View File

@ -16,7 +16,6 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
@ -216,7 +215,7 @@ public class LevelsManagerTest {
islands.add(il);
}
// Supply no island levels first (for migrate), then islands
when(handler.loadObjects()).thenReturn(Collections.emptyList(), islands);
when(handler.loadObjects()).thenReturn(islands);
when(handler.objectExists(anyString())).thenReturn(true);
when(levelsData.getLevel()).thenReturn(-5L, -4L, -3L, -2L, -1L, 0L, 1L, 2L, 3L, 4L, 5L, 45678L);
when(levelsData.getUniqueId()).thenReturn(uuid.toString());
@ -231,7 +230,7 @@ public class LevelsManagerTest {
when(iwm.getPermissionPrefix(any())).thenReturn("bskyblock.");
lm = new LevelsManager(addon);
lm.migrate();
}
/**