ModularJobs
Developer

API contracts

The public dev.mintychochip API — services, registries, and extension points.

The api module (dev.mintychochip.*) is the integration surface for dependent plugins. It has no Paper dependency. Integrators start with the Bridge facade, which is populated when the plugin enables.

Bridge

Bridge.bridge() // throws IllegalStateException when plugin is not enabled

Exposes: registryContainer(), actionService(), conditionFactory(), boostFactory(), timedBoostDataService(), economy(): Optional<EconomyProvider>, jobService(), professionService(), recipeService(), buffService(), stationService(), nodeHarvestService(), eventBus().

Example:

var jobs = Bridge.bridge().jobService().getJobs();
var events = Bridge.bridge().eventBus();

Core models

TypeWhat it is
JobOne complete rooted job tree: jobKey(), rootNode(), nodes(), node(...), pathTo(...), plus tree-wide leveling and payout rules
JobNodeOne root or specialization definition: owning jobKey(), nodeKey(), nullable parentKey(), and presentation
JobTaskNode-owned record: nodeKey, actionTypeKey, contextKey, payables; key() is the action/context inheritance key
PlayerJobStateImmutable player snapshot: complete job(), active currentNode(), tree-wide experience/level, withExperience(...), and withCurrentNode(...)
LevelingCurvePure deterministic evaluate(Parameters) — the tree-wide leveling math extension point
PayableCurvePure deterministic payout scaling evaluate(Parameters)

Job always represents the complete tree, even when JobService.getJob(...) is called with a specialization key. JobNode.parentKey() describes the root-to-specialization chain. The root owns maxLevel(), levelingCurve(), and payableCurves().

One PlayerJobState exists per player and root job tree. Changing its active node with withCurrentNode(...) preserves tree-wide XP and level. Tasks inherit along Job.pathTo(...): the nearest descendant definition with the same action/context key replaces the complete ancestor task; non-conflicting ancestor tasks remain visible.

Perk unlocks are Paper configuration data, not public Job state. They follow the same root-to-active-node path at runtime.

Purity rules

Curves must be deterministic pure functions of their parameters. Player-state updates return immutable snapshots; persist the returned PlayerJobState.

Payables

TypeWhat it is
PayablePresentation-neutral record: type(), amount()
PayableTypeKeyed, registry-backed; handler() provides payout logic
PayableTypesBuilt-ins: EXPERIENCE, ECONOMY
PayableHandlerPrimary payout extension point — pay(PayableContext)
PayableRendererPresentation boundary — render(Payable) returns an Adventure component
PayableAmountSealed: value() + optional currency()
Currencymodularjobs:… namespace presets via of(identifier, symbol)
EconomyProviderisCurrencySupported(Currency) + deposit(...) — the ledger bridge

Custom payable types are registry entries, not direct interface implementations: register a PayableType whose handler performs the payout.

Payable and PayableType contain no rendering behavior. Presentation surfaces receive a PayableRenderer separately.

Actions & contexts

TypeWhat it is
ActionTypeKeyed, @NonExtendable; registry-backed
ActionTypes20+ built-ins: block_place, block_break, tnt_break, kill, dye, strip_log, craft, fish, smelt, brew, enchant, repair, breed, tame, shear, milk, explore, eat, collect, bake, bucket, brush, wax, villager_trade
ActionServiceRegisters custom action types and reports player actions through the normal reward/player-state path
ContextSealed task-input records: BlockContext, ItemContext, EntityContext, DyeContext, EnchantmentContext, PotionContext, ChunkContext, and arbitrary KeyContext (+ deprecated MaterialContext)

Contexts and actions are sealed/@NonExtendable by design — the registry is the extension mechanism.

Custom actions

Register a custom action once while your dependent plugin enables, then keep the returned ActionType and report each occurrence through the same service:

ActionService actions = Bridge.bridge().actionService();

ActionType questComplete =
    actions.register(
        Key.key("myplugin", "quest_complete"),
        "Quest Complete");

actions.report(
    player.getUniqueId(),
    questComplete,
    Key.key("myplugin", questId));

The last argument is the context key used to match the job task. Use this keyed form for plugin-owned identifiers such as quests or skills. Existing typed contexts remain available when the action concerns a block, item, entity, enchantment, potion, or chunk.

ActionService registration rejects duplicate keys instead of replacing the existing action, preventing accidental overwrites through this API. Reporting an unregistered action also fails immediately. Reporting runs synchronously through the normal curves, boosts, payable handlers, and player-state path.

Registries

TypeWhat it is
RegistryView<T>Read-only: get(Key), getOrThrow(Key), isRegistered, stream
Registry<T>Adds register(T) (replacement by Adventure key)
RegistryKey<T>Typed registry bucket identifier
RegistryContainerhasRegistry / getRegistry / editRegistry
RegistryKeysJOBS, PAYABLE_TYPES, ACTION_TYPES, TRANSIENT_BOOST_SOURCES
Bridge.bridge().registryContainer()
    .editRegistry(RegistryKeys.TRANSIENT_BOOST_SOURCES, reg -> reg.register(mySource));

Upgrades

TypeWhat it is
SkillTreeImmutable v2 graph: nodes, availability, purchase gating, exclusions, spend/cost math
SkillNodeRecord: kind (ROOT/SKILL/MAJOR), levels, prerequisites, requirements, exclusions, position
UpgradeServiceTree/player data retrieval, node unlock/purchase, reset lifecycle
NodeEffectSealed v2 effects: boosts, ruled boosts, permissions, recipes, capabilities, state writes
PerkPolicyMAX (highest unlocked level) vs ADDITIVE
PlayerUpgradeDataPersisted state: skill points, node levels, perks

Integrators normally consume UpgradeService and UpgradeBoostDataService; declarative node/effect/requirement implementations are where the API is sealed, so prefer composition via Bridge.

Profession catalog

TypeWhat it is
ProfessionCatalogClosed built-in catalog: tracks(), byId(), byStorageKey(), resolve(), isCanonicalTrack()
ProfessionDefinitionid, storageKey, category, displayName (normalized lowercase)
ProfessionCategoryGATHERING, PROCESSING, CRAFTING
RecipeDefinitionid, professionId, requiredLevel, tier, outputKey; craftOutputKey() exposes the effective crafted-item lookup key

Use catalog resolution in config/UI — do not extend the closed catalog.

On this page