Skip to content

Conversation

@tal5
Copy link
Member

@tal5 tal5 commented Jun 30, 2025

Welp.

Due to the large amount of changes, and a lot of the changes being intertwined with one another, I wasn't really able to split this into commits, so I'll just try and write an explanation for every part.

Note

Need to figure out how to deprecate EnchantmentScriptContainer's meta before merge.

Overall

Previously enchantments were created using an anonymous sub-class, which is not possible anymore due to them becoming records.
The new approach is just using the enchantment constructor normally, and attempting to wrangle the parameters into being the best fit for how stuff used to work.
This also means that some of the stuff that could previously hot-reload can't anymore, but I did my best to make sure that at least specifying dynamic values with tags works for the most part (and realistically production servers shouldn't be hot-reloading enchantment scripts too much anyway)

For 1.20, an anonymous sub-class is still possible, but some methods became final & got similarly replaced with constructor usage.

dist/pom.xml

  • Removed an unnecessary relocation.

slots key

1.21: Was replaced by EquipmentSlotGroup internally, and is now a list instead of an array; overall similar.

rarity key

Was replaced by weight and anvil cost values internally, so EnchantmentHelper$Rarity was created to back-support - it contains the old weight and anvil cost values for each rarity, and gets rarities from weights by matching the closest one.

category key

Replaced with HolderSet<Item> primaryItems (TagKey on 1.20) internally, and back-supported with an EnchantmentHelperImpl$EnchantmentCategory enum that holds the relevant item tag keys.

full_name key

1.21: The only option is passing in a complete component now (which gets the enchantment level appended to it), best effort back-support by removing <context.level> from the raw string (which is usually at then end of the string, where the level gets added internally) and parsing it once at load time.

min_level key

Just not a thing you can set anymore, but does default to 1 which is what most scripts probably had anyway.

max_level key

No problems, gets passed in as usual.

min_cost, max_cost keys

Costs can't be dynamic anymore, and only offer the option of a constant cost or a base cost with a certain amount added for each level above first.
Back-supported with EnchantmentHelperImpl#tryParseCost, which either:

  1. Returns a constant cost if that's the case
  2. Tries recognizing a basic <context.level.mul[X]> and return a dynamic cost.
  3. Iterates over every possible level, gets the cost for it, and sees if it can find a common multiplier to use.
  4. Uses the enchantment's average cost if all else fails.

treasure_only, is_curse, is_tradable, is_discoverable keys

1.21: Now managed by enchantment tags, which get set in DenizenEnchantmentFix using Paper's tag editing API.

can_enchant key

1.21: Is based on item type now, which means slightly regression - otherwise fully back-supported via EnchantmentHelperImpl$DynamicHolderSet, a HolderSet implementation that contains everything from a certain registry, filtered by a predicate.
1.20: Can still override the canEnchant method, but now passes in an invalid item tag (just random letters), as enchantments must also have one of these for some internal checks.

damage_bonus, damage_protection keys

1.21: No longer their own thing, and are now an "Enchantment effect" internally; back-supported via EnchantmentHelperImpl$DynamicContextAwareLevelValue, which acts both as the condition that checks the entity the effect is applying to (just returns true and stores it) and as the function that gets the effect based on the level, which lets it access the stored entity for the calculation.
Both: The entity type part is also no longer a thing, back-supported with EnchantmentHelperImpl$MobType which stores the relevant entity tag for each type and finds the current type for an entity type based on that.

after attack, after hurt keys

1.21: Can't really inject into that handling anymore, so back-supported with events in EnchantmentHelper$EnchantmentBackSupportEvents.
Also reordered params on EnchantmentSciprtContainer#doPostHurt, for easier lambda usage.

Other

  • On 1.21, to avoid the enchantments being sent to the client and it having issues/compatibility issues with the way enchantment scripts used to work, it now:
    1. Uses a custom RegistrationInfo that simulates being a vanilla enchantment, to avoid the server trying to serialize our custom enchantments.
    2. Excludes Denizen enchantments from ClientboundRegistryDataPackets; since these are sent during the configuration mode, had to take a different approach for the packet interception - EnchntmentHelperImpl$EnchantmentExcludingChannelHandler is a netty ChannelOutboundHandlerAdapter that registers itself with Paper's internal ChannelInitializeListener system, so that it exists early enough.
  • EnchantmentScriptContainer got #getKey and #getScriptFromEnchantment added, and registeredEnchantmentContainers changed due to it being accessed async now (packet code in several places), I kinda just slapped ConcurrentHashMap on it, but lmk if there's a different approach you'd prefer.
  • EnchantmentHelper$EnchantmentBackSupportEvents also has events & logic to call Player#updateInventory for stuff like grindstones/anvils, as the client seems to pre-calculate it and display no result otherwise.
  • Depends#setupEnchantmentFix is now a thing.
  • On 1.21, for enchanted items in e.g. spawn chests which are loaded before Denizen is enabled, the server errors and deletes the enchantment - this is remedied via DenizenEnchantmentFix registering dud enchantments on startup, and EnchantmentHelperImpl#replaceRegistryValue now being a thing.
  • On 1.21, EnchantmentHelper#getDamageBonus/getDamageProtection - the logic for that internally is now requires a full entity instance instead of a mob type and such, so it has best effort back-support via mocking an entity instance based on getting a MobType, choosing a random entity type from it's tag, and mocking an entity instance from that.

Note

A lot of the new reflection added here doesn't use ReflectionMappingsInfo, as this is Paper only either way & Paper runs on Mojang mappings now - this is just in the 1.21 impl.

Note

I did add a enchantmentScriptContainers deprecation warning, but not really sure how would a deprecated script container meta look - should I still remove the description like tags/mechs?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant