Quests & Goals
A quest blueprint is a template loaded from quests/*.yml: a display item, a QuestGoal describing the gameplay trigger that advances it, and per-category min/max ranges used to roll a concrete target. QuestGenerationService turns blueprints into per-season QuestInstances (see Seasons & Categories § Quest generation); as players play, GoalListener routes matching Bukkit/Astral events into QuestService.tryProgress, which advances the right instance and, on completion, credits the category's points. See Overview for how this fits into seasons and rewards, and Developer API for adding a custom goal type.
Blueprint format
Each file under quests/ is loaded as a BlueprintConfiguration — a single top-level blueprints: map whose keys are the blueprint ids:
Field | Type | Required | Description |
|---|---|---|---|
(map key) | String | Yes | The blueprint id — used by |
|
| Yes | The quest's menu item — |
| Goal object | Yes |
|
|
| Yes | Category id ( |
|
| No | Optional requirement list gating whether progress is allowed to apply at all — see below. |
QuestBlueprintTypeSerializer requires id (the map key), display, goal, and requirements to be present — a blueprint missing any of those, or whose goal.type isn't registered, fails to load with a SerializationException (that file's blueprints don't load; other files are unaffected).
requirements ranges
RequirementValues(int min, int max) is just an inclusive roll range. QuestGenerationService reads blueprint.requirements().get(categoryId) and rolls the instance's target with random.nextInt(max - min + 1) + min. A blueprint that omits a category from requirements is never picked for that category's generation. See Seasons & Categories for the full generation flow (blueprint pool selection, no-duplicate tracking, unlocksAt/expiresAt).
completion-requirements
completion-requirements is an optional PaperRequirementList (same list-of-requirement-string grammar as elsewhere in AstralCore, e.g. "permission:...", "stat:... >= ..."). It is not checked once at completion time — QuestInstance.progress(player, amount) calls isCandidate(player) on every call, and if the list is non-empty and fails, that call is a silent no-op: the event that would have advanced the quest simply doesn't count, and the player can try again on a later matching event once they satisfy the requirements. Already-earned progress is never rolled back.
Goal resolution
goal.type is a string key looked up in QuestTypeRegistry.findByName (a static NamedRegistry<Class<? extends QuestGoal<?>>> populated at class-init). QuestBlueprintTypeSerializer resolves the class, then deserializes the rest of the goal node into it via Configurate's object mapper — every built-in goal is a @ConfigSerializable record, so its constructor components (kebab-cased) become the goal: block's remaining YAML keys. An unregistered type fails blueprint loading. See Developer API § Extending the quest goal framework for registering additional goal types.
Display & progress placeholders
When a QuestInstance's display item is resolved for a player (the .item sub-key of the quest placeholder context, as used by the farmpass-main menu's quest grid — see %farmpass_quests%), QuestInstance#get registers two direct placeholders before rendering blueprint().display():
Placeholder | Resolves to |
|---|---|
| The instance's current |
| The instance's rolled |
These two only resolve inside that item-rendering context — they are not standalone %farmpass_...% placeholders (see Placeholders for the full namespace).
Block goals
break-block
Event: BlockBreakEvent
Field | Type | Description |
|---|---|---|
|
| Materials whose breaking counts toward progress. |
Ignores blocks placed by a player, if BlockService (AstralCore) is registered and reports wasPlacedByPlayer(block). If the broken block's BlockData is Ageable and not yet at its maximum age, the break is ignored unless the material is one of the stackable set below (so an immature sweet berry bush still counts, but an unripe wheat/carrot/potato does not). For the stackable set — CACTUS, SUGAR_CANE, BAMBOO, SWEET_BERRY_BUSH — progress counts the whole vertical column: starting at 1 for the broken block, it walks upward (BlockFace.UP) counting consecutive blocks of the same material and adds all of them in a single instance.progress(player, count) call.
place-block
Event: BlockPlaceEvent
Field | Type | Description |
|---|---|---|
|
| Materials that count when placed. Matched against the placed item's type; |
harvest-block
Event: PlayerHarvestBlockEvent (vanilla replant-in-place harvests: sweet berries, cave vines/glow berries, and similar)
Field | Type | Description |
|---|---|---|
|
| Progresses |
brush-block
Event: PlayerInteractEvent
Field | Type | Description |
|---|---|---|
|
| Brushable block types that count. |
Requires a RIGHT_CLICK_BLOCK interaction with the main hand holding a BRUSH, against a clicked block whose material is in blocks and whose BlockData implements Brushable. +1 progress per matching right-click (not gated on the brushing animation actually completing).
Item goals
craft-item
Event: CraftItemEvent
Field | Type | Description |
|---|---|---|
|
| Progresses |
enchant-item
Event: EnchantItemEvent
Field | Type | Description |
|---|---|---|
|
| Item materials that count. Empty matches any material. |
|
| Enchantments that count. Empty matches any enchantment. |
Progresses +1 when both conditions match: the enchanted item's material is in materials (or materials is empty), and at least one of the enchantments being added is in enchantments (or enchantments is empty).
Player goals
fishing
Event: PlayerFishEvent
Field | Type | Description |
|---|---|---|
|
| Progresses |
travel
Event: PlayerMoveEvent — no config fields.
Accumulates 3D Euclidean move distance (horizontal + vertical) per player in a static, per-UUID map, only counting moves where PlayerMoveEvent.hasExplicitlyChangedPosition() is true and the player is not flying (both checked in GoalListener before the goal ever runs). Progress advances by the truncated integer part of the accumulated distance each time it crosses a whole unit; the fractional remainder carries over to the next move. The tracked distance for a player is cleared on PlayerQuitEvent.
vote
Event: PlayerVoteEvent (AstralVote) — no config fields.
Progresses +1 on every vote.
scroll-completed
Event: ScrollCompletedEvent (AstralScrolls)
Field | Type | Description |
|---|---|---|
|
| AstralScrolls rarity ids. Progresses |
potion-effect
Event: EntityPotionEffectEvent
Field | Type | Description |
|---|---|---|
|
| Effect type → minimum level (1 = base amplifier, i.e. |
Only fires on the potion-drinker's own EntityPotionEffectEvent with Action.ADDED. Progresses +1 only if the player currently has every listed effect active at or above its configured level simultaneously (all-of, not any-of).
unlock-vault
Event: VaultChangeStateEvent
Field | Type | Description |
|---|---|---|
| boolean | Whether the vault must be an ominous (trial-key) vault to count. |
Progresses +1 when the vault's new state is UNLOCKING and its Vault.State's isOminous() matches the configured ominous value exactly. Only fires when the vault-change event has a non-null player.
player-generate-loot
Event: LootGenerateEvent — no config fields.
Progresses +1 on any loot-generation event whose associated entity is the player (e.g. opening a loot chest).
complete-tower
Event: TowerFinishedEvent (AstralTower-Bridge)
Field | Type | Description |
|---|---|---|
| int | Minimum room number. Progresses |
Entity goals
leash-entities
Event: PlayerLeashEntityEvent
Field | Type | Description |
|---|---|---|
|
| Single entity type. Progresses |
kill-entity
Event: EntityDeathEvent
Field | Type | Description |
|---|---|---|
|
| Entity types that count. |
| boolean | If |
Only routed here when the death's damage source has a Player as the causing entity (checked in GoalListener); progresses +1 per matching death.
kill-multiple-entities
Event: ArrowKillMultipleEntitiesEvent (custom — see Developer API § ArrowKillMultipleEntitiesEvent)
Field | Type | Description |
|---|---|---|
|
| Entity types counted from the arrow's per-type kill map. |
ArrowPiercingListener tracks every non-Trident AbstractArrow a player shoots and, once it comes to rest, fires ArrowKillMultipleEntitiesEvent if that single arrow killed 2 or more entities total. The goal sums the kill counts for entity types present in entities and adds the sum as one instance.progress(player, totalKills) call.
breed-entity
Event: EntityBreedEvent
Field | Type | Description |
|---|---|---|
|
| Single entity type. Progresses |
fertilize-egg
Event: EntityFertilizeEggEvent
Field | Type | Description |
|---|---|---|
|
| Single entity type. Progresses |
Progression & completion
GoalListener subscribes at EventPriority.MONITOR (ignoreCancelled = true on every handler except the plain PlayerQuitEvent cleanup) to the Bukkit/Astral events above and forwards each into QuestService.tryProgress(player, GoalClass, event). For every one of the player's quest instances, tryProgress:
Skips the instance if it's
null, alreadycompleted(), not yetunlocked(), orexpired().Skips it unless its blueprint's
goalis exactly an instance of the passedGoalClassand itseventType()matches the fired event's runtime class.Calls
goal.handle(player, instance, event), which is what actually callsinstance.progress(...)(subject tocompletion-requirements).If the instance is now
completed(), credits the category'srewarded-points(see Seasons & Categories § Categories) to the player'sFarmPassData.points()and sends thequest-completedmessage (see Configuration) with%name%and%points%placeholders.
A Caffeine cache (recentlyRewarded, key "<playerUUID>:<questInstanceId>", 5-second expiry) guards step 4 against awarding the same completed instance twice if multiple matching events fire in quick succession before the instance's state is otherwise re-checked.
Example blueprint
From quests/simple.yml, a break-block quest with a daily range of 5–15 and a weekly range of 16–25:
Rolled into a daily instance, a player breaking this blueprint's target between 5 and 15 fully-grown wheat crops completes it; rolled into a weekly instance, the target is between 16 and 25.
Further reading
Overview — core concepts and architecture.
Seasons & Categories — how blueprints become dated quest instances.
Rewards — spending the points quest completion awards.
Placeholders —
%farmpass_quests%and the quest item-rendering context.Developer API —
QuestService,BlueprintService, and registering custom goal types.