Skip to content

Add dispensers config option to disable dispenser logging (fixes #727) - #989

Merged
Intelli merged 6 commits into
PlayPro:masterfrom
ThoriaDevelopment:dispensers-config-option
Sep 15, 2026
Merged

Intelli merged 6 commits into
PlayPro:masterfrom
ThoriaDevelopment:dispensers-config-option

Conversation

@ThoriaDevelopment

@ThoriaDevelopment ThoriaDevelopment commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Adds a dispensers config option that controls whether CoreProtect logs the world changes a dispenser causes. It defaults to true, so existing installations keep their current behavior. Fixes #727.

Problem

#727 asks for a way to stop dispensers from writing a row for every block they change, which adds up fast on farms, without turning off player block logging. Setting block-place: false stops those rows today, but it stops player block logging with them. A comment in the issue also asks for dispensed bone meal.

Fix

Config.java gains a DISPENSERS field, a dispensers entry in the generated config.yml, and a header explaining it, placed after pistons.

Three listeners read it.

BlockDispenseListener returns before queueing any world change when the option is off. The return sits after the dispenser item transaction and after the bone meal hand-off, so neither is affected.

BlockFertilizeListener skips growth attributed to a dispenser. The hand-off in BlockDispenseListener records the block a dispenser's bone meal was applied to, and it does so only when the block is an actual dispenser, so a dropper ejecting bone meal cannot claim a block that another plugin then grows. Bone meal that other plugins apply without a player stays #bonemeal and is still logged.

BlockIgniteListener skips ignition caused by a dispenser. Flint and steel is matched through getIgnitingBlock(). A fire charge becomes a fireball that reports no igniting block, so it is matched through the fireball's shooter being a BlockProjectileSource, which still identifies the dispenser after the block itself has been broken.

Testing

Build: mvn -B verify on JDK 25, BUILD SUCCESS.

Live: Paper 26.2 build 123, flat world, DuckDB storage, CoreProtect built from this branch. I drove the server over RCON and read the plugin's DuckDB tables afterwards. A helper plugin applies bone meal with no player for the other-plugin cases, including from a dropper's own dispense event when testing whether a dropper can claim the block.

With dispensers: true:

  • A dropper ejecting bone meal at (300,-59,450) grew nothing on its own, and bone meal that the helper plugin then applied to (310,-60,450) in the same tick was logged as #bonemeal. Before this revision that row was logged as #dispenser.
  • A bone meal dispenser grew the wheat at (320,-60,450) and logged #dispenser there.
  • A flint and steel dispenser lit fire at (331,-60,450), logged as #fire.
  • A fire charge dispenser wrote #fire at (340,-60,450) with the dispenser in place, and at (351,-60,450) when the dispenser was removed while the fireball was still in the air. The removal was confirmed with execute if block before the fireball landed.
  • Bone meal applied by the helper plugin with no dispenser involved wrote #bonemeal at (360,-60,450).
  • Water and lava buckets dispensed into enclosed pockets placed water at (390,-60,450) and lava at (395,-60,450), both logged as #dispenser. Water dispensed onto open ground at (400,-60,450) was logged as #dispenser, and the blocks the water spread into were logged as #water.

With dispensers: false, applied by /co reload:

  • The bone meal dispenser at (320,-60,470), the flint and steel dispenser at (331,-60,470), the fire charge dispenser at (340,-60,470), and the water and lava buckets at (390,-60,470) and (395,-60,470) all changed the world (checked with execute if block) and wrote no block rows.
  • The fire charge whose dispenser was removed before impact wrote no block rows, while the ignition still fired.
  • Bone meal applied by the helper plugin at (360,-60,470) still wrote #bonemeal, and the water flowing from (400,-60,470) still wrote #water rows.
  • Dispenser and dropper item transactions were still written in both configurations.

Not tested: entities spawned by dispensers, and per-world configuration files.

Notes

Dispenser and dropper item transactions stay under item-transactions, and entities that dispensers spawn stay under entity-spawns. Water or lava that flows after a dispenser places the source is still logged under water-flow and lava-flow, like any other source. Only the initial ignition is attributed to the dispenser; fire that spreads afterwards has no dispenser source and is logged as before.

TNT primed by a dispenser's flint and steel keeps its current attribution. Both the TNT break row and the explosion rows are written as #tnt, and both follow the explosions option: they disappear with explosions: false and stay in place with dispensers: false. The dispenser is not named on those rows.

docs/config.md does not list the logging options, so the option is documented in its config.yml header. Happy to add a section to that page as well.

@Intelli

Intelli commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Please ensure that the bone-meal filter identifies actual dispenser activity instead of suppressing all #bonemeal events, which also hides growth caused by other plugins. Also apply dispensers: false to dispenser ignition in BlockIgniteListener.

@ThoriaDevelopment

Copy link
Copy Markdown
Contributor Author

The bone meal filter now only covers actual dispenser activity, and BlockIgniteListener skips dispenser ignition.

The hand-off in BlockDispenseListener was gated on !dispenseSuccess, and on Paper 26.2 the dispense event's velocity is the target block coordinates. That made dispenseSuccess true for every successful dispense, so the cache write never ran and dispensed growth fell through to #bonemeal. The cache write now runs for every dispensed bone meal, so BlockFertilizeListener attributes the growth to #dispenser, and the check only skips #dispenser. Bone meal that other plugins apply without a player stays #bonemeal and is still logged while the option is off.

BlockIgniteListener now skips dispenser ignition when the option is off, covering both paths: flint-and-steel, where getIgnitingBlock() is the dispenser, and fire charges, where the fireball's shooter is the dispenser's BlockProjectileSource.

Testing:

  • Build: mvn -B clean verify on JDK 25, BUILD SUCCESS.
  • Live: Paper 26.2 build 123, flat world, DuckDB, the branch build. A small helper plugin applied bone meal with no player for the "other plugin" case.

With dispensers: true, a bone meal dispenser grew wheat at (600,-60,300) and wrote #dispenser rows there, while plugin-applied bone meal at (613,-60,300) wrote #bonemeal rows. A flint-and-steel dispenser wrote #fire at (606,-60,300), and a fire charge dispenser wrote #fire at (800,-60,300).

With dispensers: false, applied by /co reload: the bone meal dispenser at (700,-59,300), the flint-and-steel dispenser at (705,-60,300), and the fire charge dispenser at (820,-59,300) all fired (their co_container rows are present) but wrote no block rows. The plugin-applied bone meal at (713,-60,300) still wrote its #bonemeal rows.

Not tested: droppers, lava, and entities spawned by dispensers.

@Intelli

Intelli commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Please restrict bone-meal tracking to actual dispensers so a dropper ejecting bone meal cannot accidentally prevent CoreProtect from logging plant growth caused by another plugin. Also ensure dispensers: false still prevents fire-charge ignition logging if the dispenser is removed before impact.

@ThoriaDevelopment

Copy link
Copy Markdown
Contributor Author

Bone meal tracking now requires the block to be an actual dispenser, and fire charge ignition no longer depends on the dispenser still being there.

Bone meal

Droppers report the same Dispenser block data as dispensers, so checking only material == Material.BONE_MEAL claimed the block a dropper faced even though a dropper ejects the item as an entity. A plugin growing that block in the same tick then inherited the #dispenser attribution, which hid the growth while dispensers: false and mislabeled it while dispensers: true. The condition now also requires block.getType() == Material.DISPENSER.

I reproduced the old behavior with a test plugin that applies bone meal to the faced block from its own dispense listener, running after CoreProtect's. With dispensers: false the growth at (430,-60,430) was missing from the database entirely. With dispensers: true the growth at (450,-60,440) was written as #dispenser. After the change both are written as #bonemeal, and a real dispenser still writes #dispenser for the wheat it grows at (320,-60,450).

Fire charge

Fireball ignitions report no igniting block, so the check fell back to the shooter's block at the moment of impact. That is whatever occupies the dispenser's position by then, and it is air once the dispenser has been broken. The check now asks whether the fireball's shooter is a BlockProjectileSource, which is set when the dispenser fires and is not affected by the block being removed later.

Reproduced first: a dispenser at (350,-40,420) with a fire charge, powered and then removed before the fireball landed, wrote a #fire row at (348,-60,421) with dispensers: false. With the change the same setup writes no row, the ignition still happens (the probe logged cause=FIREBALL with a CraftBlockProjectileSource shooter), and dispensers: true still writes #fire for it.

Droppers cannot launch projectiles, so the shooter check cannot pick one up: a dropper holding a fire charge ejected it as an item, with no projectile spawn event and no ignition.

Verification

  • Build: mvn -B verify on JDK 25, BUILD SUCCESS.
  • Live: Paper 26.2 build 123, flat world, DuckDB, this branch. Both option values were exercised in one session, the second after /co reload.
  • With dispensers: false: the dispenser's own bone meal, flint and steel, fire charge and bucket placements all changed the world (checked with execute if block) and wrote no block rows. The plugin's bone meal at (360,-60,470) still wrote #bonemeal, the water flowing from (400,-60,470) still wrote #water, and dispenser item transactions were still written.
  • With dispensers: true: the full matrix is in the pull request description.
  • Not tested: entities spawned by dispensers, and per-world configuration files.

One scope note from the same run: TNT primed by a dispenser's flint and steel still writes its block row as #tnt under explosions, with the option on and off. That path is attributed to #tnt rather than to the dispenser, so it stays with the explosions option. I have added it to the description rather than extending the diff.

@Intelli

Intelli commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Thanks for addressing the dispenser/dropper distinction and fire-charge ignition after the dispenser is removed.

Please clarify the dispensers config header: it controls direct dispenser block changes, including bucket use, ignition, and bone-meal growth. Subsequent fluid flow, fire spread, and TNT priming/explosions remain controlled by their existing options.

Keep the current TNT behavior and attribution unchanged; no need to extend this PR into the TNT listeners.

@ThoriaDevelopment

Copy link
Copy Markdown
Contributor Author

The dispensers header now states the scope you described. It reads:

# Logs direct block changes caused by dispensers: bucket use, ignition,
# and the growth caused by dispensed bone meal. Fluid flow, fire spread,
# and TNT priming and explosions remain controlled by their own options.

This commit only changes that header string in Config.java. BlockDispenseListener, BlockIgniteListener, and BlockFertilizeListener are untouched, and no TNT listener was added, so TNT primed by a dispenser's flint and steel keeps its #tnt attribution under the explosions option as before.

The header is emitted through the existing multi-line header path, the same one used by entries such as database-type and natural-break. New installations and configs missing the dispensers key get the new text. A config.yml that already contains the key keeps its current comment, because headers are only written for options that are not yet present.

Build: mvn -B clean verify on JDK 25, BUILD SUCCESS.

@Intelli
Intelli merged commit 0ac4761 into PlayPro:master Sep 15, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a config option to disable block dispense event logs being saved

2 participants