fix(heights): repair native stature potion brewing
Release / release (push) Successful in 6m27s
CI / build (push) Successful in 2m8s

This commit is contained in:
dmg
2026-09-11 22:16:48 -04:00
parent ff0523860c
commit df44bba9c6
7 changed files with 593 additions and 1 deletions
@@ -43,6 +43,7 @@ public final class SpigotHeightsPlugin extends JavaPlugin {
potions = new PotionRecipes(this);
potions.register();
getServer().getPluginManager().registerEvents(new StatureBrewing(potions), this);
getServer().getPluginManager().registerEvents(
new StatureListener(potions, stature::drink, stature::resume,
task -> getServer().getScheduler().runTask(this, task)), this);
@@ -0,0 +1,89 @@
package games.dmg.spigotheights;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.BrewEvent;
/** Completes authenticated stature conversions at the native brewing transaction boundary. */
public final class StatureBrewing implements Listener {
private final PotionRecipes potions;
public StatureBrewing(PotionRecipes potions) {
this.potions = potions;
}
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
public void detachResults(BrewEvent event) {
if (event.isCancelled()) {
return;
}
try {
// Native no-op results can mirror the input itself. Detach before normal result modifiers
// run, otherwise an in-place result edit also changes the inventory despite cancellation.
for (int slot = 0; slot < Math.min(3, event.getResults().size()); slot++) {
ItemStack input = event.getContents().getItem(slot);
if (potions.identify(input) != null && input.equals(event.getResults().get(slot))) {
event.getResults().set(slot, input.clone());
}
}
} catch (RuntimeException exception) {
event.setCancelled(true);
throw exception;
}
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void brew(BrewEvent event) {
try {
convert(event);
} catch (RuntimeException exception) {
// Paper logs listener exceptions and continues dispatch: cancel first to prevent item loss.
event.setCancelled(true);
throw exception;
}
}
private void convert(BrewEvent event) {
ItemStack ingredient = event.getContents().getIngredient();
if (event.isCancelled() || ingredient == null) {
return;
}
Material source;
Material target;
if (ingredient.getType() == Material.GUNPOWDER) {
source = Material.POTION;
target = Material.SPLASH_POTION;
} else if (ingredient.getType() == Material.DRAGON_BREATH) {
source = Material.SPLASH_POTION;
target = Material.LINGERING_POTION;
} else {
return;
}
for (int slot = 0; slot < 3; slot++) {
ItemStack input = event.getContents().getItem(slot);
StaturePotion kind = potions.identify(input);
if (kind != null && input.getType() == source) {
if (slot >= event.getResults().size() || input.getAmount() != 1) {
event.setCancelled(true);
return;
}
ItemStack result = event.getResults().get(slot);
if (result != null && result.getType() == target
&& result.getAmount() == 1 && potions.identify(result) == kind) {
continue;
}
if (!input.equals(result)) {
// Do not overwrite another plugin's output or spend ingredients on a failed conversion.
event.setCancelled(true);
return;
}
// 2618 recognizes custom recipes but mix() returns base-less potions unchanged.
// Correct only that no-op; leave inventory and ingredient accounting to the server.
event.getResults().set(slot, potions.create(kind, target));
}
}
}
}