You need a Dropper and a DropHandler.
Check out any of the implemented Droppers to see how I’ve implemented; the one for captured is a great bare minimum example. You need the ResourceLocation for the trigger, the loot tables that’ll be dropped from, the conditions to evaluate on each event instance, and the optional drop target.
You can add more properties if you’d like; check out the one for ticked if you want something more involved.
Make a codec to parse the user’s datapack data in. If you’re doing bare minimum, just use the same thing as capture does and point at your own class for the getter.
The context class does the heavy lifting for translating the data from an event into loot params. Aim for LootContextParams.ORIGIN and LootContextParams.THIS_ENTITY if possible, as it lets users target location and the primary entity for the event with vanilla/other mods’ conditions. From there, see if it makes sense to add any from DropLootTables.LootParams so that the mod’s conditions can be applied.
Check out CapturedHandler for a straightforward example or DefeatedHandler for one with more complex logic (handling base drops). This object is responsible for catching the game event, finding the relevant configured Droppers, and executing the drop logic.
You will need to implement the DropHandler<Context, Dropper, Event> interface with your specific types:
Context class defined inside your Dropper.Dropper class.PokemonCapturedEvent).dropperTypeId: This must match the trigger ResourceLocation defined in your Dropper. This is how the system knows which JSON files belong to this handler.dropTargetTypes: A map of valid targets for this event. You provide a factory function (lambda) that takes the event and returns a DropTarget?.
PLAYER_INVENTORY -> PlayerDropTarget(evt.player).getContext(evt): Initialize and return your Dropper's Context using data from the event.getLevel(evt): Extract the ServerLevel from the event. This is critical for generating loot.isRelevantEvent(evt) (Optional): Override this if you need to filter events early (e.g., ensuring a player is actually involved).processOtherDrops(evt) (Optional): Override this if you want to support preserve_base_drops. You will need to manually extract duplicate logic from other handlers currently.