Skip to content

Commit b755519

Browse files
authored
Merge pull request #412 from BentoBoxWorld/feature/block-donation
Add block donation feature
2 parents 045a634 + 099fc44 commit b755519

35 files changed

Lines changed: 4007 additions & 1037 deletions

CLAUDE.md

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,100 @@ JaCoCo coverage reports are generated during `mvn verify`.
8989
| `locales/` | `src/main/resources/locales/` | Translation strings |
9090
| `panels/` | `src/main/resources/panels/` | GUI layout definitions |
9191

92+
**Panel template upgrades:** Files under `panels/` are copied to the addon's data folder (`plugins/BentoBox/addons/Level/panels/`) on first run and are **not** overwritten on upgrade. If a release modifies a panel template (new tabs, buttons, slots, etc.), the release notes/changelog must explicitly instruct users to delete the affected on-disk panel file so it regenerates — otherwise existing servers will silently keep the old layout.
93+
94+
**Current upgrade-sensitive example:** If `src/main/resources/panels/detail_panel.yml` changes (for example by adding a new `DONATED` tab), existing servers must delete/regenerate `plugins/BentoBox/addons/Level/panels/detail_panel.yml` after upgrading or they will continue using the old panel definition and the new tab will not appear.
9295
## Code Conventions
9396

9497
- Null safety via Eclipse JDT annotations (`@NonNull`, `@Nullable`) — honour these on public APIs
9598
- BentoBox framework patterns: `CompositeCommand` for commands, `@ConfigEntry`/`@ConfigComment` for config, `@StoreAt` for database objects
9699
- Pre- and post-events (`IslandPreLevelEvent`, `IslandLevelCalculatedEvent`) follow BentoBox's cancellable event pattern — fire both when adding new calculation triggers
100+
101+
## Dependency Source Lookup
102+
103+
When you need to inspect source code for a dependency (e.g., BentoBox, addons):
104+
105+
1. **Check local Maven repo first**: `~/.m2/repository/` — sources jars are named `*-sources.jar`
106+
2. **Check the workspace**: Look for sibling directories or Git submodules that may contain the dependency as a local project (e.g., `../bentoBox`, `../addon-*`)
107+
3. **Check Maven local cache for already-extracted sources** before downloading anything
108+
4. Only download a jar or fetch from the internet if the above steps yield nothing useful
109+
110+
Prefer reading `.java` source files directly from a local Git clone over decompiling or extracting a jar.
111+
112+
In general, the latest version of BentoBox should be targeted.
113+
114+
## Project Layout
115+
116+
Related projects are checked out as siblings under `~/git/`:
117+
118+
**Core:**
119+
- `bentobox/` — core BentoBox framework
120+
121+
**Game modes:**
122+
- `addon-acidisland/` — AcidIsland game mode
123+
- `addon-bskyblock/` — BSkyBlock game mode
124+
- `Boxed/` — Boxed game mode (expandable box area)
125+
- `CaveBlock/` — CaveBlock game mode
126+
- `OneBlock/` — AOneBlock game mode
127+
- `SkyGrid/` — SkyGrid game mode
128+
- `RaftMode/` — Raft survival game mode
129+
- `StrangerRealms/` — StrangerRealms game mode
130+
- `Brix/` — plot game mode
131+
- `parkour/` — Parkour game mode
132+
- `poseidon/` — Poseidon game mode
133+
- `gg/` — gg game mode
134+
135+
**Addons:**
136+
- `addon-level/` — island level calculation
137+
- `addon-challenges/` — challenges system
138+
- `addon-welcomewarpsigns/` — warp signs
139+
- `addon-limits/` — block/entity limits
140+
- `addon-invSwitcher/` / `invSwitcher/` — inventory switcher
141+
- `addon-biomes/` / `Biomes/` — biomes management
142+
- `Bank/` — island bank
143+
- `Border/` — world border for islands
144+
- `Chat/` — island chat
145+
- `CheckMeOut/` — island submission/voting
146+
- `ControlPanel/` — game mode control panel
147+
- `Converter/` — ASkyBlock to BSkyBlock converter
148+
- `DimensionalTrees/` — dimension-specific trees
149+
- `discordwebhook/` — Discord integration
150+
- `Downloads/` — BentoBox downloads site
151+
- `DragonFights/` — per-island ender dragon fights
152+
- `ExtraMobs/` — additional mob spawning rules
153+
- `FarmersDance/` — twerking crop growth
154+
- `GravityFlux/` — gravity addon
155+
- `Greenhouses-addon/` — greenhouse biomes
156+
- `IslandFly/` — island flight permission
157+
- `IslandRankup/` — island rankup system
158+
- `Likes/` — island likes/dislikes
159+
- `Limits/` — block/entity limits
160+
- `lost-sheep/` — lost sheep adventure
161+
- `MagicCobblestoneGenerator/` — custom cobblestone generator
162+
- `PortalStart/` — portal-based island start
163+
- `pp/` — pp addon
164+
- `Regionerator/` — region management
165+
- `Residence/` — residence addon
166+
- `TopBlock/` — top ten for OneBlock
167+
- `TwerkingForTrees/` — twerking tree growth
168+
- `Upgrades/` — island upgrades (Vault)
169+
- `Visit/` — island visiting
170+
- `weblink/` — web link addon
171+
- `CrowdBound/` — CrowdBound addon
172+
173+
**Data packs:**
174+
- `BoxedDataPack/` — advancement datapack for Boxed
175+
176+
**Documentation & tools:**
177+
- `docs/` — main documentation site
178+
- `docs-chinese/` — Chinese documentation
179+
- `docs-french/` — French documentation
180+
- `BentoBoxWorld.github.io/` — GitHub Pages site
181+
- `website/` — website
182+
- `translation-tool/` — translation tool
183+
184+
Check these for source before any network fetch.
185+
186+
## Key Dependencies (source locations)
187+
188+
- `world.bentobox:bentobox``~/git/bentobox/src/`

pom.xml

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
<mock-bukkit.version>v1.21-SNAPSHOT</mock-bukkit.version>
5858
<!-- More visible way how to change dependency versions -->
5959
<paper.version>1.21.11-R0.1-SNAPSHOT</paper.version>
60-
<bentobox.version>3.10.2</bentobox.version>
60+
<bentobox.version>3.14.1-SNAPSHOT</bentobox.version>
6161
<!-- Warps addon version -->
6262
<warps.version>1.12.0</warps.version>
6363
<!-- Visit addon version -->
@@ -69,7 +69,7 @@
6969
<!-- Do not change unless you want different name for local builds. -->
7070
<build.number>-LOCAL</build.number>
7171
<!-- This allows to change between versions. -->
72-
<build.version>2.23.0</build.version>
72+
<build.version>2.24.0</build.version>
7373
<sonar.projectKey>BentoBoxWorld_Level</sonar.projectKey>
7474
<sonar.organization>bentobox-world</sonar.organization>
7575
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
@@ -222,7 +222,7 @@
222222
<dependency>
223223
<groupId>world.bentobox</groupId>
224224
<artifactId>bentobox</artifactId>
225-
<version>3.10.0</version>
225+
<version>${bentobox.version}</version>
226226
</dependency>
227227
<dependency>
228228
<groupId>world.bentobox</groupId>
@@ -378,25 +378,26 @@
378378
<plugin>
379379
<groupId>org.apache.maven.plugins</groupId>
380380
<artifactId>maven-clean-plugin</artifactId>
381-
<version>3.4.0</version>
381+
<version>3.5.0</version>
382382
</plugin>
383383
<plugin>
384384
<groupId>org.apache.maven.plugins</groupId>
385385
<artifactId>maven-resources-plugin</artifactId>
386-
<version>3.3.1</version>
386+
<version>3.5.0</version>
387387
</plugin>
388388
<plugin>
389389
<groupId>org.apache.maven.plugins</groupId>
390390
<artifactId>maven-compiler-plugin</artifactId>
391-
<version>3.14.1</version>
391+
<version>3.15.0</version>
392392
<configuration>
393393
<release>${java.version}</release>
394+
<fork>true</fork>
394395
</configuration>
395396
</plugin>
396397
<plugin>
397398
<groupId>org.apache.maven.plugins</groupId>
398399
<artifactId>maven-surefire-plugin</artifactId>
399-
<version>3.5.4</version>
400+
<version>3.5.5</version>
400401
<configuration>
401402
<argLine>
402403
${argLine}
@@ -434,12 +435,12 @@
434435
<plugin>
435436
<groupId>org.apache.maven.plugins</groupId>
436437
<artifactId>maven-jar-plugin</artifactId>
437-
<version>3.4.2</version>
438+
<version>3.5.0</version>
438439
</plugin>
439440
<plugin>
440441
<groupId>org.apache.maven.plugins</groupId>
441442
<artifactId>maven-javadoc-plugin</artifactId>
442-
<version>3.11.1</version>
443+
<version>3.12.0</version>
443444
<configuration>
444445
<doclint>none</doclint> <!-- Turnoff all checks -->
445446
<failOnError>false</failOnError>
@@ -458,7 +459,7 @@
458459
<plugin>
459460
<groupId>org.apache.maven.plugins</groupId>
460461
<artifactId>maven-source-plugin</artifactId>
461-
<version>3.3.1</version>
462+
<version>3.4.0</version>
462463
<executions>
463464
<execution>
464465
<id>attach-sources</id>
@@ -471,17 +472,17 @@
471472
<plugin>
472473
<groupId>org.apache.maven.plugins</groupId>
473474
<artifactId>maven-install-plugin</artifactId>
474-
<version>3.1.3</version>
475+
<version>3.1.4</version>
475476
</plugin>
476477
<plugin>
477478
<groupId>org.apache.maven.plugins</groupId>
478479
<artifactId>maven-deploy-plugin</artifactId>
479-
<version>3.1.3</version>
480+
<version>3.1.4</version>
480481
</plugin>
481482
<plugin>
482483
<groupId>org.apache.maven.plugins</groupId>
483484
<artifactId>maven-shade-plugin</artifactId>
484-
<version>3.6.0</version>
485+
<version>3.6.2</version>
485486
<configuration>
486487
<minimizeJar>true</minimizeJar>
487488
<artifactSet>
@@ -513,7 +514,7 @@
513514
<plugin>
514515
<groupId>org.jacoco</groupId>
515516
<artifactId>jacoco-maven-plugin</artifactId>
516-
<version>0.8.13</version>
517+
<version>0.8.14</version>
517518
<configuration>
518519
<append>true</append>
519520
<excludes>

src/main/java/world/bentobox/level/Level.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import java.util.UUID;
88

99
import org.bukkit.Bukkit;
10+
import org.bukkit.Material;
1011
import org.bukkit.World;
1112
import org.bukkit.configuration.InvalidConfigurationException;
1213
import org.bukkit.configuration.file.YamlConfiguration;
@@ -17,8 +18,10 @@
1718
import world.bentobox.bentobox.api.addons.Addon;
1819
import world.bentobox.bentobox.api.addons.GameModeAddon;
1920
import world.bentobox.bentobox.api.configuration.Config;
21+
import world.bentobox.bentobox.api.flags.Flag;
2022
import world.bentobox.bentobox.api.user.User;
2123
import world.bentobox.bentobox.database.objects.Island;
24+
import world.bentobox.bentobox.managers.RanksManager;
2225
import world.bentobox.bentobox.util.Util;
2326
import world.bentobox.level.calculators.Pipeliner;
2427
import world.bentobox.level.commands.AdminLevelCommand;
@@ -27,6 +30,7 @@
2730
import world.bentobox.level.commands.AdminStatsCommand;
2831
import world.bentobox.level.commands.AdminTopCommand;
2932
import world.bentobox.level.commands.IslandDetailCommand;
33+
import world.bentobox.level.commands.IslandDonateCommand;
3034
import world.bentobox.level.commands.IslandLevelCommand;
3135
import world.bentobox.level.commands.IslandTopCommand;
3236
import world.bentobox.level.commands.IslandValueCommand;
@@ -49,6 +53,17 @@ public class Level extends Addon {
4953
// The 10 in top ten
5054
public static final int TEN = 10;
5155

56+
/**
57+
* Flag to control who can donate blocks to raise island level.
58+
* Default: OWNER only. Can be extended down to MEMBER rank.
59+
*/
60+
public static final Flag BLOCK_DONATION = new Flag.Builder("ISLAND_BLOCK_DONATION", Material.HOPPER)
61+
.type(Flag.Type.PROTECTION)
62+
.defaultRank(RanksManager.OWNER_RANK)
63+
.minimumRank(RanksManager.MEMBER_RANK)
64+
.mode(Flag.Mode.BASIC)
65+
.build();
66+
5267
// Settings
5368
private ConfigSettings settings;
5469
private Config<ConfigSettings> configObject = new Config<>(this, ConfigSettings.class);
@@ -114,6 +129,9 @@ public void allLoaded() {
114129
hookPlugin("RoseStacker", this::hookRoseStackers);
115130
hookPlugin("UltimateStacker", this::hookUltimateStacker);
116131

132+
// Register the block donation flag
133+
getPlugin().getFlagsManager().registerFlag(this, BLOCK_DONATION);
134+
117135
if (this.isEnabled()) {
118136
hookExtensions();
119137
}
@@ -250,6 +268,7 @@ private void registerCommands(GameModeAddon gm) {
250268
new IslandTopCommand(this, playerCmd);
251269
new IslandValueCommand(this, playerCmd);
252270
new IslandDetailCommand(this, playerCmd);
271+
new IslandDonateCommand(this, playerCmd);
253272
});
254273
}
255274

src/main/java/world/bentobox/level/LevelsManager.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ private boolean addToTopTen(Island island, long lv) {
8282
&& hasTopTenPerm(island.getWorld(), island.getOwner())) {
8383
topTenLists.computeIfAbsent(island.getWorld(), k -> new TopTenData(island.getWorld())).getTopTen()
8484
.put(island.getUniqueId(), lv);
85+
// Invalidate the cache for this world because of the update
86+
cache.remove(island.getWorld());
8587
return true;
8688
}
8789
return false;
@@ -540,4 +542,54 @@ public void deleteIsland(String uniqueId) {
540542
handler.deleteID(uniqueId);
541543
}
542544

545+
// ---- Block Donation Methods ----
546+
547+
/**
548+
* Record a block donation for an island. Items should already be removed from the player's inventory.
549+
*
550+
* @param island the island receiving the donation
551+
* @param donorUUID UUID of the donating player
552+
* @param material the material name being donated
553+
* @param count how many blocks
554+
* @param points the point value of this donation
555+
*/
556+
public void donateBlocks(@NonNull Island island, @NonNull UUID donorUUID, @NonNull String material, int count, long points) {
557+
IslandLevels ld = getLevelsData(island);
558+
ld.addDonation(donorUUID.toString(), material, count, points);
559+
handler.saveObjectAsync(ld);
560+
}
561+
562+
/**
563+
* Queue a full level recalculation for the island. Call this after donations
564+
* so that the level/top-ten update immediately.
565+
*
566+
* @param island the island to recalculate
567+
*/
568+
public void recalculateAfterDonation(@NonNull Island island) {
569+
UUID owner = island.getOwner();
570+
if (owner != null) {
571+
calculateLevel(owner, island);
572+
}
573+
}
574+
575+
/**
576+
* Get the total donated points for an island.
577+
*
578+
* @param island the island
579+
* @return total donated points
580+
*/
581+
public long getDonatedPoints(@NonNull Island island) {
582+
return getLevelsData(island).getDonatedPoints();
583+
}
584+
585+
/**
586+
* Get the donated blocks map for an island.
587+
*
588+
* @param island the island
589+
* @return map of material name to count
590+
*/
591+
public Map<String, Integer> getDonatedBlocks(@NonNull Island island) {
592+
return getLevelsData(island).getDonatedBlocks();
593+
}
594+
543595
}

src/main/java/world/bentobox/level/calculators/IslandLevelCalculator.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,22 @@ private List<String> getReport() {
287287
+ " blocks (max " + limit + explain);
288288
}
289289
reportLines.add(LINE_BREAK);
290+
// Donated blocks section
291+
if (results.donatedPoints.get() > 0) {
292+
reportLines.add("Donated blocks (permanent contributions):");
293+
reportLines.add("Total donated points = " + String.format("%,d", results.donatedPoints.get()));
294+
Map<String, Integer> donatedBlocks = addon.getManager().getDonatedBlocks(island);
295+
donatedBlocks.entrySet().stream()
296+
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
297+
.forEach(entry -> {
298+
Integer value = addon.getBlockConfig().getBlockValues().getOrDefault(entry.getKey().toLowerCase(java.util.Locale.ENGLISH), 0);
299+
long totalValue = (long) value * entry.getValue();
300+
reportLines.add(" " + Util.prettifyText(entry.getKey()) + " x "
301+
+ String.format("%,d", entry.getValue())
302+
+ " = " + String.format("%,d", totalValue) + " points");
303+
});
304+
reportLines.add(LINE_BREAK);
305+
}
290306
return reportLines;
291307
}
292308

@@ -701,6 +717,11 @@ public void tidyUp() {
701717
results.rawBlockCount
702718
.addAndGet((long) (results.underWaterBlockCount.get() * addon.getSettings().getUnderWaterMultiplier()));
703719

720+
// Add donated block points (permanent contributions that persist across recalculations)
721+
long donatedPoints = addon.getManager().getDonatedPoints(island);
722+
results.rawBlockCount.addAndGet(donatedPoints);
723+
results.donatedPoints.set(donatedPoints);
724+
704725
// Set the death penalty
705726
if (this.addon.getSettings().isSumTeamDeaths()) {
706727
for (UUID uuid : this.island.getMemberSet()) {

0 commit comments

Comments
 (0)