Avoid in-place Vector mutation in Crossbow ricochet calculations
In CrossbowsManager.handleRicochet(), Bukkit Vector arithmetic methods (subtract, multiply) mutate the receiver in place. The reflection formula previously mutated both arrowInBlockVector and normal, and then reused the mutated normal to compute inverseNormal.
While the existing production path produces new Vector instances for projectile hits, mutating arguments passed into a public method is unsafe for future callers and external consumers.
This change: - Clones vectors before in-place mutations (subtract, multiply) so that supplied arguments and intermediate vectors remain unmodified. - Eliminates inverseNormal by directly comparing reflectedDirection.angle(normal), matching the comment description and specular reflection geometry. - Avoids Vector.normalize() on the surface normal, avoiding potential NaN values if a non-cardinal zero vector is supplied. - Adds a unit test ensuring that neither the arrow velocity nor the surface normal vector is mutated during handleRicochet().
Minecraft 26.3 made brewing recipes data-driven, so they now sit in the server's recipe manager. Spigot 26.3 has no Bukkit counterpart for them: BrewingRecipe does not implement toBukkitRecipe, so Server.recipeIterator() throws AbstractMethodError from next() for every brewing recipe.
SalvageConfig counts ingredients through SkillUtils.getRepairAndSalvageQuantities while loading, which walks that iterator, so onEnable died and mcMMO disabled itself. Spigot's Server.getRecipesFor walks the same iterator, so ItemUtils.isSmelted would have thrown at runtime on furnace extraction as well.
RecipeUtils.safeRecipeIterator wraps the server iterator and skips recipes the server cannot convert. CraftBukkit advances its underlying iterator before converting, so a failed entry is already consumed and the walk continues with the next one. Both call sites use it, and hasOreSmeltingRecipe filters by result type itself instead of calling getRecipesFor.
Servers that convert every recipe (Paper 26.3, and Paper and Spigot 1.20.6) see the same recipes in the same order as before.
Count repair ingredients once per repairable instead of on every repair
SimpleRepairable.getMinimumQuantity fell through to SkillUtils.getRepairAndSalvageQuantities whenever MinimumQuantity was not configured, and RepairManager reaches it through getBaseRepairDurability on every anvil repair. That method converts every recipe on the server to its Bukkit form, around two thousand of them, and on Spigot 26.3 each walk also throws and catches an AbstractMethodError per brewing recipe.
The recipe count is now kept in a field after the first repair of that item type. The field is a plain int: a count is always at least one, zero means not counted yet, and an int write is atomic, so a racing region thread either counts for itself or reads a usable value. Recipes changed after the first repair are no longer picked up, which matches Salvage, where the count has always been taken once at startup.
A configured quantity of zero or less now falls back to the recipe count as well. It is the divisor of the base repair amount, and only a configured zero was mapped to the fallback before, so a negative value in repair.vanilla.yml or a zero passed to RepairableFactory by another plugin produced a negative repair or a division by zero. (commit: 5f2e9f8)
Only tolerate AbstractMethodError in the recipe walk and log what was skipped
safeRecipeIterator also swallowed UnsupportedOperationException, which no server is known to throw from its recipe iterator. Swallowing it hid unrelated failures, and because a skipped entry is assumed to be consumed, an iterator that keeps throwing without advancing turned the walk into an endless loop. Only the AbstractMethodError that Spigot 26.3 produces is skipped now; anything else reaches the caller.
A walk that completes after skipping recipes writes one debug line with the count and the first failure, shown when General.Verbose_Logging is on. A server that failed to convert a crafting recipe would otherwise lower Repair and Salvage quantities with nothing in the log to explain it. Walks that stop early, like the ore smelting lookup, stay quiet since their count would be partial. The iterator takes the logger as a parameter for this. (commit: 13ea4c3)