Astral Realms Documentation Help

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:

blueprints: <blueprint-id>: display: { } # ItemStackWrapper goal: type: "<goal-type>" # looked up in QuestTypeRegistry # ...goal-specific fields requirements: daily: min: 5 max: 15 weekly: min: 16 max: 25 completion-requirements: # optional - "permission:farmpass.vip"

Field

Type

Required

Description

(map key)

String

Yes

The blueprint id — used by BlueprintService.findById, quest-generation blueprint pools, and duplicate-tracking. If the same id appears in more than one file, the later-loaded file silently wins (BlueprintService.load() iterates every file in the folder and overwrites by id).

display

ItemStackWrapper

Yes

The quest's menu item — material, name, lore, etc. Same shape used everywhere else in AstralCore; see Menu Items. Lore can use the %required%/%total% placeholders.

goal

Goal object

Yes

type selects the QuestGoal implementation from QuestTypeRegistry; the remaining keys are goal-specific and deserialize directly into that goal's fields — see Goal resolution and the catalogue below.

requirements

Map<String, RequirementValues>

Yes

Category id (daily/weekly, i.e. a categories.yml key) → {min, max} inclusive range. A blueprint is only eligible for a category's quest generation if it declares a range for that category id.

completion-requirements

PaperRequirementList

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

requirements: daily: min: 5 max: 15

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.

completion-requirements: - "permission:farmpass.vip"

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

%required%

The instance's current progress (int).

%total%

The instance's rolled target (int).

lore: - "Harvest %required%/%total% wheat to complete this quest."

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

goal: type: "break-block" blocks: - "wheat"

Field

Type

Description

blocks

List<Material>

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

goal: type: "place-block" materials: - "oak_sapling"

Field

Type

Description

materials

List<Material>

Materials that count when placed. Matched against the placed item's type; +1 progress per matching place.

harvest-block

Event: PlayerHarvestBlockEvent (vanilla replant-in-place harvests: sweet berries, cave vines/glow berries, and similar)

goal: type: "harvest-block" materials: - "sweet_berries"

Field

Type

Description

materials

Set<Material>

Progresses +1 if any of the event's harvested items match one of these materials.

brush-block

Event: PlayerInteractEvent

goal: type: "brush-block" blocks: - "suspicious_sand"

Field

Type

Description

blocks

List<Material>

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

goal: type: "craft-item" materials: - "bread"

Field

Type

Description

materials

List<Material>

Progresses +1 if the clicked recipe's result material matches.

enchant-item

Event: EnchantItemEvent

goal: type: "enchant-item" materials: - "diamond_sword" enchantments: - "sharpness"

Field

Type

Description

materials

Set<Material>

Item materials that count. Empty matches any material.

enchantments

Set<Enchantment>

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

goal: type: "fishing" materials: - "cod"

Field

Type

Description

materials

List<Material>

Progresses +1 when the fish event's state is CAUGHT_FISH and the caught entity is an Item whose material matches.

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)

goal: type: "scroll-completed" rarities: - "legendary"

Field

Type

Description

rarities

Set<String>

AstralScrolls rarity ids. Progresses +1 if the completed scroll's rarity id is in the set.

potion-effect

Event: EntityPotionEffectEvent

goal: type: "potion-effect" potions: strength: 2 speed: 1

Field

Type

Description

potions

Map<PotionEffectType, Integer>

Effect type → minimum level (1 = base amplifier, i.e. amplifier + 1).

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

goal: type: "unlock-vault" ominous: false

Field

Type

Description

ominous

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)

goal: type: "complete-tower" room: 5

Field

Type

Description

room

int

Minimum room number. Progresses +1 if the finished run's room() is >= this value.

Entity goals

leash-entities

Event: PlayerLeashEntityEvent

goal: type: "leash-entities" type: "horse"

Field

Type

Description

type

EntityType

Single entity type. Progresses +1 when the leashed entity's type matches.

kill-entity

Event: EntityDeathEvent

goal: type: "kill-entity" entities: - "zombie" with-fireball: false

Field

Type

Description

entities

Set<EntityType>

Entity types that count.

with-fireball

boolean

If true, the kill must be an indirect death whose direct damage entity is a Fireball (e.g. a ghast/blaze fireball).

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)

goal: type: "kill-multiple-entities" entities: - "zombie" - "skeleton"

Field

Type

Description

entities

Set<EntityType>

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

goal: type: "breed-entity" entity: "cow"

Field

Type

Description

entity

EntityType

Single entity type. Progresses +1 when the player is the breeder and the bred entity's type matches.

fertilize-egg

Event: EntityFertilizeEggEvent

goal: type: "fertilize-egg" entity: "turtle"

Field

Type

Description

entity

EntityType

Single entity type. Progresses +1 when the player is the breeder and the egg's father's type matches.

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:

  1. Skips the instance if it's null, already completed(), not yet unlocked(), or expired().

  2. Skips it unless its blueprint's goal is exactly an instance of the passed GoalClass and its eventType() matches the fired event's runtime class.

  3. Calls goal.handle(player, instance, event), which is what actually calls instance.progress(...) (subject to completion-requirements).

  4. If the instance is now completed(), credits the category's rewarded-points (see Seasons & Categories § Categories) to the player's FarmPassData.points() and sends the quest-completed message (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:

blueprints: wheat: display: material: "wheat" name: "Wheat Farmer" lore: - "Harvest %required%/%total% wheat to complete this quest." goal: type: "break-block" blocks: - "wheat" requirements: daily: min: 5 max: 15 weekly: min: 16 max: 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 APIQuestService, BlueprintService, and registering custom goal types.

Last modified: 25 July 2026