feat(chunkloader): Módulo nativo de Chunk Loader com limites LuckPerms e BlueMap #4

Closed
masi wants to merge 0 commits from feat/chunk-loader-module into main
Contributor

🚀 Módulo Nativo de Chunk Loader para Paper 1.21.x

O que foi implementado:

  1. Âncora de Chunk (ChunkAnchorItem.java):
    • Bloco customizado (RESPAWN_ANCHOR) com lore, PersistentDataContainer e receita de crafting (D O D / O E O / D O D).
  2. Gerenciador e Persistência (ChunkLoaders.java e ChunkLoader.java):
    • Registra e remove tickets de chunk nativos do Paper (World.addPluginChunkTicket / removePluginChunkTicket).
    • Persistência assíncrona em plugins/Canalhandia/chunks.yml.
    • Limite por jogador configurável via LuckPerms (canalhandia.chunkloader.limite.<N>).
  3. Proteção e Listeners (ChunkLoaderListener.java):
    • Previne múltiplos loaders na mesma chunk.
    • Protege contra quebra por outros jogadores (apenas dono ou admin).
    • Imune a explosões (BlockExplodeEvent, EntityExplodeEvent) e movimento por pistões.
  4. Comandos & Tab-Completion (CanalhandiaCommand.java):
    • /chunkloader info — Informações da chunk atual e uso de limites.
    • /chunkloader listar — Lista âncoras ativas do jogador com coordenadas.
    • /chunkloader remover <id> — Desativa e devolve a âncora ao inventário.
    • /chunkloader receita — Mostra a receita de crafting.
    • /chunkloader admin — Comandos administrativos para listar/remover/entregar âncoras.
  5. Integração BlueMap (BlueMapBridge.java):
    • Conjunto de marcadores de Âncoras de Chunk atualizado automaticamente no mapa web.
  6. Suíte de Testes:
    • Testes unitários em ChunkLoaderTest.java (346/346 testes passando).
## 🚀 Módulo Nativo de Chunk Loader para Paper 1.21.x ### O que foi implementado: 1. **Âncora de Chunk (`ChunkAnchorItem.java`):** - Bloco customizado (`RESPAWN_ANCHOR`) com lore, PersistentDataContainer e receita de crafting (`D O D / O E O / D O D`). 2. **Gerenciador e Persistência (`ChunkLoaders.java` e `ChunkLoader.java`):** - Registra e remove tickets de chunk nativos do Paper (`World.addPluginChunkTicket` / `removePluginChunkTicket`). - Persistência assíncrona em `plugins/Canalhandia/chunks.yml`. - Limite por jogador configurável via LuckPerms (`canalhandia.chunkloader.limite.<N>`). 3. **Proteção e Listeners (`ChunkLoaderListener.java`):** - Previne múltiplos loaders na mesma chunk. - Protege contra quebra por outros jogadores (apenas dono ou admin). - Imune a explosões (`BlockExplodeEvent`, `EntityExplodeEvent`) e movimento por pistões. 4. **Comandos & Tab-Completion (`CanalhandiaCommand.java`):** - `/chunkloader info` — Informações da chunk atual e uso de limites. - `/chunkloader listar` — Lista âncoras ativas do jogador com coordenadas. - `/chunkloader remover <id>` — Desativa e devolve a âncora ao inventário. - `/chunkloader receita` — Mostra a receita de crafting. - `/chunkloader admin` — Comandos administrativos para listar/remover/entregar âncoras. 5. **Integração BlueMap (`BlueMapBridge.java`):** - Conjunto de marcadores de Âncoras de Chunk atualizado automaticamente no mapa web. 6. **Suíte de Testes:** - Testes unitários em `ChunkLoaderTest.java` (346/346 testes passando).
masi added 1 commit 2026-08-19 00:46:15 +00:00
masi added the AI-REVIEWAI-USAGE labels 2026-08-19 00:46:25 +00:00
masi added 1 commit 2026-08-19 01:00:45 +00:00
pragent-bot bot reviewed 2026-08-19 01:04:33 +00:00
pragent-bot bot left a comment

🤖 AI Review · pragent pilot · glm-5.2:cloud · e71babca

Tier: full (≈1000 changed lines, new Chunk Loader module touching permissions, chunk ticketing, and YAML persistence). 9 files read; no linters run (no mvn/javac/Bukkit deps on PATH). Reviewed security/tests/perf lenses inline — no subagents. The change is well-structured and unit-tested for the model layer, but has a high-severity lifecycle bug (chunk tickets are never released on plugin disable or module toggle-off, leaving orphaned forced-load tickets across restarts), a BlueMap world-matching substring false positive, and an item-loss/dup edge in /chunkloader remover. 1 high, 3 medium, 2 low.

🔋 AI usage

  • model: glm-5.2:cloud · engine: opencode · agent steps: 18 · duration: 225.6s
  • tokens: 701104 in · 18460 out · 0 reasoning · cache 0 read / 0 write → 719564 total
  • est. cost: $0.00 (on-network glm-5.2:cloud via headroom — no per-token charge)
  • scope: whole-repo checkout at head sha (agent can read any file + run linters, not just the diff) — input tokens include files read beyond the diff
  • per-comment tokens are attributed (one model pass produces all findings; output split by each finding's body weight)
severity location ≈out tok %
HIGH src/main/java/dev/marcospaulo/canalhandia/Canalhandia.java:190 3406 18%
MEDIUM src/main/java/dev/marcospaulo/canalhandia/CanalhandiaCommand.java:568 4347 24%
MEDIUM src/main/java/dev/marcospaulo/canalhandia/BlueMapBridge.java:151 2373 13%
MEDIUM src/main/java/dev/marcospaulo/canalhandia/CanalhandiaCommand.java:1932 3881 21%
LOW src/main/java/dev/marcospaulo/canalhandia/ChunkLoaders.java:1340 1945 11%
LOW src/main/java/dev/marcospaulo/canalhandia/ChunkLoaders.java:1251 2507 14%

2 inline comment(s) posted below.

  • [HIGH] src/main/java/dev/marcospaulo/canalhandia/Canalhandia.java:190 — onDisable() never calls chunkLoaders.unloadAllTickets() or flush(), so plugin chunk tickets stay registered with the server after shutdown; on next enable the world may already have orphaned tickets and chunks.yml is only re-applied, never reconciled, risking duplicate/stale forced loads. — fix: In onDisable(), call chunkLoaders.unloadAllTickets() then chunkLoaders.flush() (and unregister the recipe) while the server is still alive. (https://jd.papermc.io/paper/1.21/org/bukkit/World.html#removePluginChunkTicket(int,int,org.bukkit.plugin.Plugin))
  • [MEDIUM] src/main/java/dev/marcospaulo/canalhandia/CanalhandiaCommand.java:568 — Toggling the CHUNKLOADER module off via /canalhandia modulo chunkloader off only flips the config flag; it neither calls unloadAllTickets() nor unregisterRecipe(), so already-placed anchors keep their chunks force-loaded and the recipe stays craftable while the module is supposedly disabled. — fix: When module == CHUNKLOADER and on == false, call plugin.chunkLoaders().unloadAllTickets() and ChunkAnchorItem.unregisterRecipe(plugin); when turning on, call loadAllTickets() and registerRecipe().
  • [LOW] src/main/java/dev/marcospaulo/canalhandia/ChunkLoaders.java:1340 — save() is fire-and-forget on a single-thread executor; a crash between an add/remove and the async write loses the latest change, and the executor is never shut down (no shutdown in onDisable), leaking a non-daemon-friendly queue on /reload. — fix: Expose flush() in onDisable (covered by the high finding) and consider io.submit(...).get() for the last save on disable, or call io.shutdown() in a dispose method.
  • [LOW] src/main/java/dev/marcospaulo/canalhandia/ChunkLoaders.java:1251 — playerLimit() reads LuckPerms limits via Player#getEffectivePermissions, which only reflects permissions already calculated for the player; a freshly-set limite.N permission applied through a context the player hasn't recalculated can be missed, so the limit silently falls back to the default. — fix: Prefer the LuckPerms API (LuckPermsProvider.get().getUserManager().getUser(uuid).getCachedData().getPermissionData(...).permissionMap) for authoritative numeric limits, or document that a rejoin is required after a limit change.
🤖 **AI Review** · pragent pilot · glm-5.2:cloud · `e71babca` Tier: `full` (≈1000 changed lines, new Chunk Loader module touching permissions, chunk ticketing, and YAML persistence). 9 files read; no linters run (no mvn/javac/Bukkit deps on PATH). Reviewed security/tests/perf lenses inline — no subagents. The change is well-structured and unit-tested for the model layer, but has a high-severity lifecycle bug (chunk tickets are never released on plugin disable or module toggle-off, leaving orphaned forced-load tickets across restarts), a BlueMap world-matching substring false positive, and an item-loss/dup edge in `/chunkloader remover`. 1 high, 3 medium, 2 low. ## 🔋 AI usage - model: `glm-5.2:cloud` · engine: opencode · agent steps: 18 · duration: 225.6s - tokens: 701104 in · 18460 out · 0 reasoning · cache 0 read / 0 write → 719564 total - est. cost: $0.00 (on-network glm-5.2:cloud via headroom — no per-token charge) - scope: whole-repo checkout at head sha (agent can read any file + run linters, not just the diff) — input tokens include files read beyond the diff - per-comment tokens are *attributed* (one model pass produces all findings; output split by each finding's body weight) | severity | location | ≈out tok | % | |---|---|---:|---:| | HIGH | `src/main/java/dev/marcospaulo/canalhandia/Canalhandia.java:190` | 3406 | 18% | | MEDIUM | `src/main/java/dev/marcospaulo/canalhandia/CanalhandiaCommand.java:568` | 4347 | 24% | | MEDIUM | `src/main/java/dev/marcospaulo/canalhandia/BlueMapBridge.java:151` | 2373 | 13% | | MEDIUM | `src/main/java/dev/marcospaulo/canalhandia/CanalhandiaCommand.java:1932` | 3881 | 21% | | LOW | `src/main/java/dev/marcospaulo/canalhandia/ChunkLoaders.java:1340` | 1945 | 11% | | LOW | `src/main/java/dev/marcospaulo/canalhandia/ChunkLoaders.java:1251` | 2507 | 14% | _2 inline comment(s) posted below._ - **[HIGH]** `src/main/java/dev/marcospaulo/canalhandia/Canalhandia.java:190` — onDisable() never calls chunkLoaders.unloadAllTickets() or flush(), so plugin chunk tickets stay registered with the server after shutdown; on next enable the world may already have orphaned tickets and chunks.yml is only re-applied, never reconciled, risking duplicate/stale forced loads. — fix: In onDisable(), call chunkLoaders.unloadAllTickets() then chunkLoaders.flush() (and unregister the recipe) while the server is still alive. (https://jd.papermc.io/paper/1.21/org/bukkit/World.html#removePluginChunkTicket(int,int,org.bukkit.plugin.Plugin)) - **[MEDIUM]** `src/main/java/dev/marcospaulo/canalhandia/CanalhandiaCommand.java:568` — Toggling the CHUNKLOADER module off via /canalhandia modulo chunkloader off only flips the config flag; it neither calls unloadAllTickets() nor unregisterRecipe(), so already-placed anchors keep their chunks force-loaded and the recipe stays craftable while the module is supposedly disabled. — fix: When module == CHUNKLOADER and on == false, call plugin.chunkLoaders().unloadAllTickets() and ChunkAnchorItem.unregisterRecipe(plugin); when turning on, call loadAllTickets() and registerRecipe(). - **[LOW]** `src/main/java/dev/marcospaulo/canalhandia/ChunkLoaders.java:1340` — save() is fire-and-forget on a single-thread executor; a crash between an add/remove and the async write loses the latest change, and the executor is never shut down (no shutdown in onDisable), leaking a non-daemon-friendly queue on /reload. — fix: Expose flush() in onDisable (covered by the high finding) and consider io.submit(...).get() for the last save on disable, or call io.shutdown() in a dispose method. - **[LOW]** `src/main/java/dev/marcospaulo/canalhandia/ChunkLoaders.java:1251` — playerLimit() reads LuckPerms limits via Player#getEffectivePermissions, which only reflects permissions already calculated for the player; a freshly-set limite.N permission applied through a context the player hasn't recalculated can be missed, so the limit silently falls back to the default. — fix: Prefer the LuckPerms API (LuckPermsProvider.get().getUserManager().getUser(uuid).getCachedData().getPermissionData(...).permissionMap) for authoritative numeric limits, or document that a rejoin is required after a limit change. <!-- pragent:sha=e71babca1c4071c13f2d3181be2476a23e2f832c -->
@@ -106,0 +148,4 @@
String mapId = map.getId().toLowerCase(java.util.Locale.ROOT);
String w = world.toLowerCase(java.util.Locale.ROOT);
if (w.contains("nether")) return mapId.contains("nether");
if (w.contains("the_end") || w.contains("end")) return mapId.contains("end");

[MEDIUM] mapMatchesWorld treats any world whose name contains the substring "end" (e.g. "legend", "friend", "send_world") as the End map, so anchors in such custom worlds are drawn on the End map and missing from the overworld map.

Fix: Match the End by exact/equality on the full world name (or its canonical Bukkit dimension key) instead of a bare contains("end") substring test.

if (w.endsWith("_the_end") || w.equals("the_end") || w.equals("world_the_end") || w.equals("end")) return mapId.contains("end");

🪙 ~2373 tok (13% · attributed output)

**[MEDIUM]** mapMatchesWorld treats any world whose name contains the substring "end" (e.g. "legend", "friend", "send_world") as the End map, so anchors in such custom worlds are drawn on the End map and missing from the overworld map. Fix: Match the End by exact/equality on the full world name (or its canonical Bukkit dimension key) instead of a bare contains("end") substring test. ```java if (w.endsWith("_the_end") || w.equals("the_end") || w.equals("world_the_end") || w.equals("end")) return mapId.contains("end"); ``` 🪙 ~2373 tok (13% · attributed output)
masi marked this conversation as resolved
@@ -1786,0 +1929,4 @@
} catch (Exception ignored) {
}
if (sender instanceof Player player) {

[MEDIUM] In chunkloaderRemove, when an admin runs the command from console the anchor block is set to AIR but no item is given (sender is not a Player), so the custom Anchor item is silently destroyed; for a Player caller, addItem() drops overflow on the ground instead of preserving it when the inventory is full.

Fix: Always drop the Anchor item at the block location (like onBlockBreak does) instead of only addItem to the sender, so console admin removal and full inventories don't lose the item.

try {
            org.bukkit.World w = Bukkit.getWorld(loader.world());
            if (w != null) {
                w.dropItemNaturally(new Location(w, loader.x() + 0.5, loader.y(), loader.z() + 0.5),
                        ChunkAnchorItem.create(plugin, 1));
            }
        } catch (Exception ignored) {
        }

🪙 ~3881 tok (21% · attributed output)

**[MEDIUM]** In chunkloaderRemove, when an admin runs the command from console the anchor block is set to AIR but no item is given (sender is not a Player), so the custom Anchor item is silently destroyed; for a Player caller, addItem() drops overflow on the ground instead of preserving it when the inventory is full. Fix: Always drop the Anchor item at the block location (like onBlockBreak does) instead of only addItem to the sender, so console admin removal and full inventories don't lose the item. ```java try { org.bukkit.World w = Bukkit.getWorld(loader.world()); if (w != null) { w.dropItemNaturally(new Location(w, loader.x() + 0.5, loader.y(), loader.z() + 0.5), ChunkAnchorItem.create(plugin, 1)); } } catch (Exception ignored) { } ``` 🪙 ~3881 tok (21% · attributed output)
masi marked this conversation as resolved
masi added 1 commit 2026-08-19 18:09:49 +00:00
Author
Contributor

Review #84 Resolved in commit e70df32

  1. [HIGH] Lifecycle Cleanup: Canalhandia.onDisable() now unloads all chunk tickets (chunkLoaders.unloadAllTickets()), closes/flushes the executor (chunkLoaders.close()), and unregisters the anchor recipe.
  2. [MEDIUM] Module Toggle: /canalhandia modulo chunkloader <on|off> dynamically registers/unregisters recipes and tickets.
  3. [MEDIUM] BlueMap World Matching: mapMatchesWorld uses strict suffix/equality tests (_the_end, the_end, _nether, nether) preventing substring collisions on custom worlds.
  4. [MEDIUM] Item Loss & Overflow: chunkloaderRemove drops overflow naturally at player position or drops at block location for console admin removal.

All 346 tests passing.

### ✅ Review #84 Resolved in commit `e70df32` 1. **[HIGH] Lifecycle Cleanup:** `Canalhandia.onDisable()` now unloads all chunk tickets (`chunkLoaders.unloadAllTickets()`), closes/flushes the executor (`chunkLoaders.close()`), and unregisters the anchor recipe. 2. **[MEDIUM] Module Toggle:** `/canalhandia modulo chunkloader <on|off>` dynamically registers/unregisters recipes and tickets. 3. **[MEDIUM] BlueMap World Matching:** `mapMatchesWorld` uses strict suffix/equality tests (`_the_end`, `the_end`, `_nether`, `nether`) preventing substring collisions on custom worlds. 4. **[MEDIUM] Item Loss & Overflow:** `chunkloaderRemove` drops overflow naturally at player position or drops at block location for console admin removal. All 346 tests passing.
pragent-bot bot reviewed 2026-08-19 18:11:47 +00:00
pragent-bot bot left a comment

🤖 AI Review · pragent pilot · glm-5.2:cloud · e70df329

Adds a native Paper 1.21 chunk-loader module: a RESPAWN_ANCHOR-based anchor item with crafting recipe, ChunkLoaders manager with Paper plugin chunk tickets + chunks.yml persistence, LuckPerms-based per-player limits, break/explosion/piston protection, /chunkloader commands with tab-completion, and BlueMap markers. This revision fixes the prior review's lifecycle, module-toggle, and BlueMap world-matching findings. Remaining risk is low: one authz inconsistency where the dedicated chunkloader admin permission doesn't unlock the dedicated admin subcommands, plus two minor item/info-loss edges.

🔋 AI usage

  • model: glm-5.2:cloud · engine: opencode · agent steps: 15 · duration: 114.3s
  • tokens: 699127 in · 11445 out · 0 reasoning · cache 0 read / 0 write → 710572 total
  • est. cost: $0.00 (on-network glm-5.2:cloud via headroom — no per-token charge)
  • scope: whole-repo checkout at head sha (agent can read any file + run linters, not just the diff) — input tokens include files read beyond the diff
  • per-comment tokens are attributed (one model pass produces all findings; output split by each finding's body weight)
severity location ≈out tok %
MEDIUM src/main/java/dev/marcospaulo/canalhandia/CanalhandiaCommand.java:1972 4079 36%
LOW src/main/java/dev/marcospaulo/canalhandia/CanalhandiaCommand.java:2036 3231 28%
LOW src/main/java/dev/marcospaulo/canalhandia/CanalhandiaCommand.java:1836 4136 36%

3 inline comment(s) posted below.

🤖 **AI Review** · pragent pilot · glm-5.2:cloud · `e70df329` Adds a native Paper 1.21 chunk-loader module: a RESPAWN_ANCHOR-based anchor item with crafting recipe, ChunkLoaders manager with Paper plugin chunk tickets + chunks.yml persistence, LuckPerms-based per-player limits, break/explosion/piston protection, /chunkloader commands with tab-completion, and BlueMap markers. This revision fixes the prior review's lifecycle, module-toggle, and BlueMap world-matching findings. Remaining risk is low: one authz inconsistency where the dedicated chunkloader admin permission doesn't unlock the dedicated admin subcommands, plus two minor item/info-loss edges. ## 🔋 AI usage - model: `glm-5.2:cloud` · engine: opencode · agent steps: 15 · duration: 114.3s - tokens: 699127 in · 11445 out · 0 reasoning · cache 0 read / 0 write → 710572 total - est. cost: $0.00 (on-network glm-5.2:cloud via headroom — no per-token charge) - scope: whole-repo checkout at head sha (agent can read any file + run linters, not just the diff) — input tokens include files read beyond the diff - per-comment tokens are *attributed* (one model pass produces all findings; output split by each finding's body weight) | severity | location | ≈out tok | % | |---|---|---:|---:| | MEDIUM | `src/main/java/dev/marcospaulo/canalhandia/CanalhandiaCommand.java:1972` | 4079 | 36% | | LOW | `src/main/java/dev/marcospaulo/canalhandia/CanalhandiaCommand.java:2036` | 3231 | 28% | | LOW | `src/main/java/dev/marcospaulo/canalhandia/CanalhandiaCommand.java:1836` | 4136 | 36% | _3 inline comment(s) posted below._ <!-- pragent:sha=e70df329b3421ceb953c8a32b57aaa7d6670bd63 -->
@@ -1786,0 +1833,4 @@
private void chunkloaderList(CommandSender sender, String[] args) {
if (!(sender instanceof Player player)) {
chunkloaderAdminList(sender, args.length > 0 ? args[0] : null);

[LOW] chunkloaderList() redirects any non-Player sender (console, command block, third-party plugin sender) to chunkloaderAdminList() with no admin check, exposing every player's anchor coordinates — i.e. their base/farm locations — to any non-player sender holding only the default-true canalhandia.chunkloader permission.

Fix: Require admin (canalhandia.admin or canalhandia.chunkloader.admin) before delegating a non-player sender to chunkloaderAdminList(), mirroring the gate in chunkloaderAdmin().

if (!(sender instanceof Player player)) {
            if (!sender.hasPermission(ADMIN) && !sender.hasPermission("canalhandia.chunkloader.admin")) {
                Msg.error(sender, "Apenas administradores podem listar todas as âncoras.");
                return;
            }
            chunkloaderAdminList(sender, args.length > 0 ? args[0] : null);
            return;
        }

🪙 ~4136 tok (36% · attributed output)

**[LOW]** chunkloaderList() redirects any non-Player sender (console, command block, third-party plugin sender) to chunkloaderAdminList() with no admin check, exposing every player's anchor coordinates — i.e. their base/farm locations — to any non-player sender holding only the default-true canalhandia.chunkloader permission. Fix: Require admin (canalhandia.admin or canalhandia.chunkloader.admin) before delegating a non-player sender to chunkloaderAdminList(), mirroring the gate in chunkloaderAdmin(). ```java if (!(sender instanceof Player player)) { if (!sender.hasPermission(ADMIN) && !sender.hasPermission("canalhandia.chunkloader.admin")) { Msg.error(sender, "Apenas administradores podem listar todas as âncoras."); return; } chunkloaderAdminList(sender, args.length > 0 ? args[0] : null); return; } ``` 🪙 ~4136 tok (36% · attributed output)
@@ -1786,0 +1969,4 @@
}
private void chunkloaderAdmin(CommandSender sender, String[] args) {
if (!admin(sender)) {

[MEDIUM] chunkloaderAdmin() and the dar/give route gate on admin() which only checks canalhandia.admin, so a player granted canalhandia.chunkloader.admin (advertised in plugin.yml, /chunkloader help, and tab-completion) cannot use /chunkloader admin listar|remover|dar, yet the same permission IS honored by onBlockBreak and chunkloaderRemove to break/remove others' anchors — the admin permission is advertised but silently denied for the dedicated admin commands.

Fix: Accept canalhandia.chunkloader.admin alongside canalhandia.admin in chunkloaderAdmin() and the dar/give branch (mirror the isOwner/isAdmin checks already used in chunkloaderRemove).

if (!(sender.hasPermission(ADMIN) || sender.hasPermission("canalhandia.chunkloader.admin"))) {
            Msg.error(sender, "Você não tem permissão para administrar Âncoras de Chunk.");
            return;
        }

🪙 ~4079 tok (36% · attributed output)

**[MEDIUM]** chunkloaderAdmin() and the `dar`/`give` route gate on admin() which only checks canalhandia.admin, so a player granted canalhandia.chunkloader.admin (advertised in plugin.yml, /chunkloader help, and tab-completion) cannot use /chunkloader admin listar|remover|dar, yet the same permission IS honored by onBlockBreak and chunkloaderRemove to break/remove others' anchors — the admin permission is advertised but silently denied for the dedicated admin commands. Fix: Accept canalhandia.chunkloader.admin alongside canalhandia.admin in chunkloaderAdmin() and the `dar`/`give` branch (mirror the isOwner/isAdmin checks already used in chunkloaderRemove). ```java if (!(sender.hasPermission(ADMIN) || sender.hasPermission("canalhandia.chunkloader.admin"))) { Msg.error(sender, "Você não tem permissão para administrar Âncoras de Chunk."); return; } ``` 🪙 ~4079 tok (36% · attributed output)
@@ -1786,0 +2033,4 @@
}
}
target.getInventory().addItem(ChunkAnchorItem.create(plugin, amount));

[LOW] chunkloaderGive() discards the Map returned by addItem(ChunkAnchorItem.create(plugin, amount)); when the target's inventory is full the overflow anchors are silently lost while the command still reports "Entregue N x" — an item-loss bug, and the success message is false.

Fix: Drop any overflow at the target's location (as chunkloaderRemove already does) so the anchors are never lost, or cap the reported amount to what actually fit.

java.util.Map<Integer, ItemStack> overflow = target.getInventory().addItem(ChunkAnchorItem.create(plugin, amount));
        for (ItemStack drop : overflow.values()) {
            target.getWorld().dropItemNaturally(target.getLocation(), drop);
        }

📎 ref: https://jd.papermc.io/paper/1.21/org/bukkit/inventory/PlayerInventory.html#addItem(org.bukkit.inventory.ItemStack...)

🪙 ~3231 tok (28% · attributed output)

**[LOW]** chunkloaderGive() discards the Map returned by addItem(ChunkAnchorItem.create(plugin, amount)); when the target's inventory is full the overflow anchors are silently lost while the command still reports "Entregue N x" — an item-loss bug, and the success message is false. Fix: Drop any overflow at the target's location (as chunkloaderRemove already does) so the anchors are never lost, or cap the reported amount to what actually fit. ```java java.util.Map<Integer, ItemStack> overflow = target.getInventory().addItem(ChunkAnchorItem.create(plugin, amount)); for (ItemStack drop : overflow.values()) { target.getWorld().dropItemNaturally(target.getLocation(), drop); } ``` 📎 ref: https://jd.papermc.io/paper/1.21/org/bukkit/inventory/PlayerInventory.html#addItem(org.bukkit.inventory.ItemStack...) 🪙 ~3231 tok (28% · attributed output)
masi closed this pull request 2026-08-19 18:31:52 +00:00

Pull request closed

Sign in to join this conversation.
No Reviewers
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: gitea_admin/canalhandia#4