Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d728b22
feat: add /island donate inv command to donate full inventory
tastybento May 4, 2026
bb9cdaf
i18n: add donate.inv translations across all locales
tastybento May 4, 2026
d0c8b85
refactor: extract [material] and [points] placeholders to constants
tastybento May 4, 2026
1c81088
Merge pull request #422 from BentoBoxWorld/feat/donate-inv-command
tastybento May 4, 2026
c531317
FIXED: Negative values in progression while using a non-linear function.
msmith-codes May 7, 2026
e9886e6
Initial plan
Copilot May 7, 2026
1c218a3
Add custom block support (Oraxen/Nexo/ItemsAdder/CraftEngine) to valu…
Copilot May 7, 2026
9c3e38a
Refactor: extract getCustomBlockItemStack helper, add null guard for …
Copilot May 7, 2026
7d5b8db
Polish: improve clarity of comments and variable names from code review
Copilot May 7, 2026
571011d
Merge pull request #423 from msmith-codes/develop
tastybento May 8, 2026
60abaf9
perf: replace O(N) linear walks with binary search in tidyUp()
tastybento May 8, 2026
d4b0277
Merge pull request #425 from BentoBoxWorld/copilot/add-custom-block-s…
tastybento May 8, 2026
58e35bb
fix: show custom-block icon and display name in value panel (#426)
tastybento May 8, 2026
beb7134
Merge pull request #427 from BentoBoxWorld/fix/426-custom-block-icon-…
tastybento May 8, 2026
9708fc0
fix: recognize CraftEngine items in value lookups (#428)
tastybento May 9, 2026
e5470ee
Merge pull request #429 from BentoBoxWorld/fix/428-craftengine-displa…
tastybento May 9, 2026
dc8f70e
fix: update BentoBox version to 3.15.1-SNAPSHOT
tastybento May 9, 2026
44009aa
Update API version in addon.yml
tastybento May 12, 2026
241017f
feat: add donations-only mode to skip block scan (#430)
tastybento May 12, 2026
c7f7bfb
Merge pull request #432 from BentoBoxWorld/feat/430-donations-only-mode
tastybento May 12, 2026
f17c70e
fix: ignore initialCount in donations-only mode (#430)
tastybento May 12, 2026
8b8b2de
fix: still run zero-island scan under donations-only (#430)
tastybento May 12, 2026
13ab09b
fix: disable VIEW action in top panel under donations-only (#430)
tastybento May 12, 2026
2f4703b
Merge pull request #433 from BentoBoxWorld/fix/430-donations-only-ign…
tastybento May 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
<mock-bukkit.version>v1.21-SNAPSHOT</mock-bukkit.version>
<!-- More visible way how to change dependency versions -->
<paper.version>1.21.11-R0.1-SNAPSHOT</paper.version>
<bentobox.version>3.15.0-SNAPSHOT</bentobox.version>
<bentobox.version>3.15.1-SNAPSHOT</bentobox.version>
<!-- Warps addon version -->
<warps.version>1.12.0</warps.version>
<!-- Visit addon version -->
Expand All @@ -69,7 +69,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.26.0</build.version>
<build.version>2.27.0</build.version>
<sonar.projectKey>BentoBoxWorld_Level</sonar.projectKey>
<sonar.organization>bentobox-world</sonar.organization>
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
Expand Down
59 changes: 58 additions & 1 deletion src/main/java/world/bentobox/level/Level.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,30 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.Plugin;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;

import com.nexomc.nexo.api.NexoItems;

import world.bentobox.bentobox.api.addons.Addon;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.configuration.Config;
import world.bentobox.bentobox.api.flags.Flag;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.hooks.CraftEngineHook;
import world.bentobox.bentobox.hooks.ItemsAdderHook;
import world.bentobox.bentobox.hooks.OraxenHook;
import world.bentobox.bentobox.managers.RanksManager;
import world.bentobox.bentobox.util.Util;
import world.bentobox.level.calculators.Pipeliner;
Expand Down Expand Up @@ -268,7 +275,11 @@ private void registerCommands(GameModeAddon gm) {
new IslandLevelCommand(this, playerCmd);
new IslandTopCommand(this, playerCmd);
new IslandValueCommand(this, playerCmd);
new IslandDetailCommand(this, playerCmd);
// In donations-only mode, there are no scanned blocks to break down,
// so the detail command is not registered.
if (!getSettings().isDonationsOnly()) {
new IslandDetailCommand(this, playerCmd);
}
new IslandDonateCommand(this, playerCmd);
});
}
Expand Down Expand Up @@ -508,6 +519,52 @@ public boolean isItemsAdder() {
return !getSettings().isDisableItemsAdder() && getPlugin().getHooks().getHook("ItemsAdder").isPresent();
}

/**
* Returns the custom-block plugin ID for an ItemStack, checking Oraxen, Nexo,
* ItemsAdder, and CraftEngine in that order. Returns {@code null} when the item
* is not recognized as a custom block by any supported plugin.
*
* @param item the ItemStack to check (may be null)
* @return a namespaced custom-block ID such as {@code "oraxen:my_block"},
* {@code "nexo:my_block"}, an ItemsAdder ID, or a CraftEngine ID
* (e.g. {@code "default:my_block"}), or {@code null}
*/
@Nullable
public String getCustomBlockId(ItemStack item) {
if (item == null || item.getType().isAir()) {
return null;
}
// Check Oraxen
if (getPlugin().getHooks().getHook("Oraxen").isPresent()) {
String id = OraxenHook.getIdByItem(item);
if (id != null) {
return "oraxen:" + id;
}
}
// Check Nexo
if (isNexo()) {
String id = NexoItems.idFromItem(item);
if (id != null) {
return "nexo:" + id;
}
}
// Check ItemsAdder
if (isItemsAdder()) {
Optional<String> id = ItemsAdderHook.getNamespacedId(item);
if (id.isPresent()) {
return id.get();
}
}
// Check CraftEngine — IDs are already namespaced (e.g. "mynamespace:my_block")
if (isCraftEngine()) {
String id = CraftEngineHook.getItemId(item);
if (id != null) {
return id;
}
}
return null;
}

/**
* @return true if the Nexo plugin is enabled and not disabled in config
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,14 @@ public IslandLevelCalculator(Level addon, Island island, CompletableFuture<Resul
*/
private long calculateLevel(final long rawPoints) {
String calcString = addon.getSettings().getLevelCalc();
// Reduce count by initial count, if zeroing is done
// Reduce count by initial count, if zeroing is done. In donations-only
// mode the initial count is ignored — it was recorded from a one-off
// scan of the starter island and would push the level wildly negative
// when subtracted from donation-only points (e.g. when an admin enables
// this mode mid-game on islands that already have an initial count).
long modifiedPoints = rawPoints
- (addon.getSettings().isZeroNewIslandLevels() ? results.initialCount.get() : 0);
- (addon.getSettings().isZeroNewIslandLevels() && !addon.getSettings().isDonationsOnly()
? results.initialCount.get() : 0);
// Paste in the values to the formula
// Use Math.max(1, ...) to prevent division by zero if island_members is used in the formula
int memberCount = Math.max(1, this.island.getMemberSet().size());
Expand Down Expand Up @@ -262,7 +267,7 @@ private List<String> getReport() {
if (addon.getSettings().isZeroNewIslandLevels()) {
reportLines.add("Initial island level = " + (0L - addon.getManager().getInitialLevel(island)));
}*/
if (addon.getSettings().isZeroNewIslandLevels()) {
if (addon.getSettings().isZeroNewIslandLevels() && !addon.getSettings().isDonationsOnly()) {
reportLines.add("Initial island count = " + (0L - addon.getManager().getInitialCount(island)));
}
reportLines.add("Previous level = " + addon.getManager().getIslandLevel(island.getWorld(), island.getOwner()));
Expand Down Expand Up @@ -750,13 +755,36 @@ public void tidyUp() {
}
this.results.level.set(calculateLevel(blockAndDeathPoints));

// Calculate how many points are required to get to the next level
long nextLevel = this.results.level.get();
long blocks = blockAndDeathPoints;
while (nextLevel < this.results.level.get() + 1 && blocks - blockAndDeathPoints < MAX_AMOUNT) {
nextLevel = calculateLevel(++blocks);
// Binary search for points to next level (first point count that exceeds currentLevel)
long currentLevel = this.results.level.get();
long lo = blockAndDeathPoints + 1;
long hi = blockAndDeathPoints + MAX_AMOUNT;
while (lo < hi) {
long mid = lo + (hi - lo) / 2;
if (calculateLevel(mid) > currentLevel) {
hi = mid;
} else {
lo = mid + 1;
}
}
this.results.pointsToNextLevel.set(lo - blockAndDeathPoints);

// Binary search for points accumulated within the current level.
// Floor at initialCount when zeroing new islands to avoid negative/NaN in non-linear formulas.
// In donations-only mode, the initial count is ignored (see calculateLevel).
long minBlocks = addon.getSettings().isZeroNewIslandLevels() && !addon.getSettings().isDonationsOnly()
? results.initialCount.get() : 0;
lo = Math.max(minBlocks, blockAndDeathPoints - MAX_AMOUNT);
hi = blockAndDeathPoints;
while (lo < hi) {
long mid = lo + (hi - lo) / 2;
if (calculateLevel(mid) >= currentLevel) {
hi = mid;
} else {
lo = mid + 1;
}
}
this.results.pointsToNextLevel.set(blocks - blockAndDeathPoints);
this.results.pointsFromCurrentLevel.set(blockAndDeathPoints - lo);

// Report
results.report = getReport();
Expand All @@ -773,6 +801,19 @@ boolean isNotZeroIsland() {
}

public void scanIsland(Pipeliner pipeliner) {
// In donations-only mode, skip the chunk scan for regular level calcs:
// tidyUp() will add the donated points and compute the level from those
// alone. Zero-island scans (run on island create/reset when
// zero-new-island-levels is true) still run the full scan so the
// initial-count handicap is recorded — this lets an admin later
// disable donations-only without losing the handicap that would
// otherwise need to be subtracted from the scanned block total.
if (addon.getSettings().isDonationsOnly() && !zeroIsland) {
pipeliner.getInProcessQueue().remove(this);
this.tidyUp();
this.getR().complete(getResults());
return;
}
// Scan the next chunk
scanNextChunk().thenAccept(result -> {
if (!Bukkit.isPrimaryThread()) {
Expand Down
23 changes: 23 additions & 0 deletions src/main/java/world/bentobox/level/calculators/Results.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ public enum Result {
AtomicLong level = new AtomicLong(0);
AtomicInteger deathHandicap = new AtomicInteger(0);
AtomicLong pointsToNextLevel = new AtomicLong(0);
/**
* Points already accumulated within the current level (i.e. how far past the
* start-of-current-level threshold the island has progressed). Combined with
* {@link #pointsToNextLevel} this gives the actual size of the current level
* interval — which is what should be shown to players when the configured
* level formula is non-linear.
*/
AtomicLong pointsFromCurrentLevel = new AtomicLong(0);
//AtomicLong initialLevel = new AtomicLong(0);
AtomicLong initialCount = new AtomicLong(0);
/**
Expand Down Expand Up @@ -115,6 +123,21 @@ public void setPointsToNextLevel(long points) {
pointsToNextLevel.set(points);
}

/**
* @return the points already accumulated within the current level
*/
public long getPointsFromCurrentLevel() {
return pointsFromCurrentLevel.get();
}

/**
* Set the points already accumulated within the current level
* @param points
*/
public void setPointsFromCurrentLevel(long points) {
pointsFromCurrentLevel.set(points);
}

/**
* @return the totalPoints
*/
Expand Down
Loading
Loading