Astral Realms Documentation Help

Developer API

AstralFarmPass exposes its services through getters on the main AstralFarmPass plugin instance, syncs per-player progress through SyncAPI/FarmPassData, and offers two real extension points for other plugins: a custom Bukkit event, and a pluggable quest-goal type registry.

Maven coordinates

<dependency> <groupId>com.astralrealms</groupId> <artifactId>farmpass</artifactId> <version>1.0-SNAPSHOT</version> <scope>provided</scope> </dependency>

Repository: https://maven.astralrealms.fr/repository/maven-public/.

Plugin services

import com.astralrealms.farmpass.AstralFarmPass; AstralFarmPass plugin = AstralFarmPass.getPlugin(AstralFarmPass.class); QuestService quests = plugin.quests(); SeasonService seasons = plugin.seasons(); SeasonRotationService seasonRot = plugin.seasonRotation(); BlueprintService blueprints = plugin.blueprints(); CategoriesService categories = plugin.categories(); MenuService menus = plugin.menus();

Getter

Type

Use

plugin.quests()

QuestService

Advance quest progress, look up a player's current quest instances, pull cached quests for a season.

plugin.seasons()

SeasonService

Look up the current season (cached or from the database).

plugin.seasonRotation()

SeasonRotationService

Force-create or re-roll the current season.

plugin.blueprints()

BlueprintService

Look up loaded quest blueprints by id or category.

plugin.categories()

CategoriesService

Look up the daily/weekly QuestCategory definitions.

plugin.menus()

MenuService

Open the farmpass-main menu for a player.

QuestService

Method

Returns

Use

tryProgress(Player player, Class<T> goalType, E event)

void

Advances every unlocked, unexpired, non-completed quest instance whose blueprint's goal is an instance of goalType and whose eventType() matches event's class; on completion, credits the category's rewarded-points and sends FarmPassMessages.QUEST_COMPLETED.

findPlayerQuests(Player player)

List<QuestInstance>

The player's FarmPassData.quests(), or an empty list if no synced data exists.

findCachedQuests(UUID seasonId)

List<QuestEntity>

Quests currently held in the in-memory Caffeine cache for a season.

findBySeason(UUID seasonId)

CompletableFuture<List<QuestEntity>>

Loads (and caches) every quest row for a season from the database.

repository()

QuestRepository

Direct database access, used internally by SeasonRotationService.

tryProgress is the actual extension point — see Extending the quest goal framework below.

SeasonService

Method

Returns

Use

getCurrentSeason()

CompletableFuture<Optional<SeasonEntity>>

Cached current season if not expired, otherwise re-queries the database.

getCurrentSeasonId()

UUID

Shortcut for the cached season's id, or null if none is active.

cachedCurrentSeason()

SeasonEntity

The raw cached value (no expiry check, no I/O) — null if none is cached.

setCurrentSeason(SeasonEntity season)

void

Overwrites the cache and, if the season hasn't already ended, preloads its quests via QuestService.findBySeason. Passing null clears the cache.

SeasonRotationService

Method

Returns

Use

createNewSeason()

CompletableFuture<SeasonEntity>

Creates a SeasonEntity for the current calendar month, generates its daily/weekly quests, and broadcasts a FarmpassUpdatedPacket. Does not check config.ymlmaster; callers are responsible for that.

rerollSeason()

CompletableFuture<SeasonEntity>

Deletes every quest and the season row for the current season, clears blueprint-rotation tracking, then calls createNewSeason(). This is what /pass reroll calls.

BlueprintService

Method

Returns

Use

findById(String id)

Optional<QuestBlueprint>

Resolve a loaded blueprint by its quests/*.yml key.

findAll()

Collection<QuestBlueprint>

Every loaded blueprint.

findByCategory(String categoryId)

List<QuestBlueprint>

Blueprints whose requirements map contains the given category id.

See Quests & Goals for the blueprint YAML shape.

Custom event: ArrowKillMultipleEntitiesEvent

com.astralrealms.farmpass.event.ArrowKillMultipleEntitiesEvent extends AstralCore's AbstractEvent (synchronous, standard HandlerList). ArrowPiercingListener tracks every non-Trident AbstractArrow shot by a player (EntityShootBowEvent) and, on each EntityDeathEvent whose direct damage source is that arrow, accumulates a kill count per EntityType. A repeating task (every 5 ticks) sweeps arrows that have come to rest (isOnGround() && isInBlock()); once an arrow is embedded, if its accumulated kills total 2 or more, the plugin fires ArrowKillMultipleEntitiesEvent for the shooting player.

Field

Type

Description

player()

Player

The player who shot the arrow.

arrow()

AbstractArrow

The arrow that came to rest (never a Trident).

kills()

Map<EntityType, Integer>

Per-entity-type kill counts credited to that single arrow.

@EventHandler public void onMultiKill(ArrowKillMultipleEntitiesEvent event) { Player player = event.player(); int total = event.kills().values().stream().mapToInt(Integer::intValue).sum(); // e.g. broadcast a highlight, grant a separate reward, ... }

GoalListener also listens for this event at MONITOR priority and routes it into QuestService.tryProgress(player, KillMultipleEntitiesGoal.class, event), driving the kill-multiple-entities quest goal.

Extending the quest goal framework

Every quest's gameplay trigger is a QuestGoal<T extends Event>:

public interface QuestGoal<T extends Event> { void handle(Player player, QuestInstance instance, T event); Class<T> eventType(); }

handle is called once per matching event with the specific QuestInstance being advanced — typically it inspects the event, computes an increment, and calls instance.progress(player, amount) (which is a no-op once the instance is already completed, or if QuestInstance.isCandidate(player) returns false because the blueprint's completion-requirements reject the player).

To add a new goal type:

  1. Implement QuestGoal<T> as a Configurate-serializable type — every built-in goal is a @ConfigSerializable record, e.g.:

    @ConfigSerializable public record MyGoal(Set<Material> materials, int multiplier) implements QuestGoal<BlockBreakEvent> { @Override public void handle(Player player, QuestInstance instance, BlockBreakEvent event) { if (materials.contains(event.getBlock().getType())) instance.progress(player, multiplier); } @Override public Class<BlockBreakEvent> eventType() { return BlockBreakEvent.class; } }

    The record's fields become the goal: block's YAML keys (alongside type:) — QuestBlueprintTypeSerializer deserializes the whole goal node into the goal class via Configurate's object mapper, so any @ConfigSerializable-compatible field type works the same way it does elsewhere in the plugins.

  2. Register the type name against the class:

    QuestTypeRegistry.register("my-goal", MyGoal.class);

    QuestTypeRegistry is a static, name-keyed registry (NamedRegistry<Class<? extends QuestGoal<?>>>). QuestBlueprintTypeSerializer reads a blueprint's goal.type string, resolves it through QuestTypeRegistry.findByName, and fails blueprint loading with a SerializationException if the name isn't registered — so register must run before BlueprintService.load() scans quests/*.yml. Since AstralFarmPass#onEnable loads blueprints as part of its own onEnable, a dependent plugin that only registers in its own onEnable will miss the initial load if it depends on AstralFarmPass (Bukkit runs FarmPass's onEnable first); register from onLoad() instead, or register in onEnable and rely on the next /pass reload to pick the type up.

  3. Drive progress for the goal's event. GoalListener only wires the goal types AstralFarmPass ships itself — for a new goal type, register your own Listener and call QuestService.tryProgress directly:

    @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onBlockBreak(BlockBreakEvent e) { plugin.quests().tryProgress(e.getPlayer(), MyGoal.class, e); }

See Quests & Goals for the full catalogue of built-in goal.type values and the blueprint YAML shape.

Per-player data: FarmPassData

Player quest progress, claimed rewards, season membership, and points are synced through AstralSync under adapter key farmpass:data (FarmPassSnapshotAdapter, Key.key("farmpass", "data")).

import com.astralrealms.farmpass.storage.FarmPassData; import com.astralrealms.sync.SyncAPI; SyncAPI.findData(player.getUniqueId(), FarmPassData.class) .ifPresent(data -> { List<QuestInstance> quests = data.quests(); Set<String> claimed = data.claimedRewards(); UUID seasonId = data.seasonId(); int points = data.points(); });

Member

Type

Description

quests()

List<QuestInstance>

The player's rolled quest instances for their current season.

claimedRewards()

Set<String>

Claimed reward keys, "<reward-id>_<free\|premium>".

seasonId()/seasonId(UUID)

UUID

The season this data belongs to; reset to null by resetSeason().

points()

int

Current point balance (read via the Lombok-generated getter).

points(IntUnaryOperator updater)

void

Mutates points through a function of the current value, e.g. data.points(i -> i + 50). This is how QuestService.tryProgress credits quest-completion rewards.

resetSeason()

void

Clears quests, claimed rewards, and points, and nulls seasonId — used by FarmPassSnapshotAdapter.update when a player's stored season no longer matches the current one.

reset()

void

Clears quests, claimed rewards, and points but leaves seasonId untouched.

FarmPassSnapshotAdapter.update runs on load and rolls a player onto the current season automatically if their stored seasonId is stale, re-populating quests() from QuestService.findCachedQuests. FarmPassSnapshotAdapter.apply additionally re-rolls any quest instance whose blueprint's requirement range for its category has since grown past the instance's target — such quests are replaced in place with a freshly-rolled target the next time the adapter runs.

Mutate FarmPassData directly for admin tooling or custom integrations; for normal player-facing flows prefer the claim-farmpass-reward action and the quest-goal framework above, which handle the completion messaging and duplicate-reward guards for you.

Registered action

AstralFarmPass#onEnable registers [claim-farmpass-reward] (ClaimRewardAction) with AstralCore's action registry, so it's usable from any config, menu, or plugin that runs Astral actions — not just the farmpass-main menu. See Rewards for the full argument reference and reward-tier YAML shape.

Consumed upstream events

GoalListener listens at MONITOR priority (ignoreCancelled = true, except the plain PlayerQuitEvent cleanup handler) for 19 events total and forwards each into QuestService.tryProgress. Most are vanilla Bukkit/Paper events; three are custom events fired by other Astral plugins, which is the pattern to follow if you want your own plugin's events to drive a quest goal (register a matching QuestGoal<T> as described above, then either add a listener of your own or depend on AstralFarmPass and PR a GoalListener entry):

Event

Source plugin

Goal driven

PlayerVoteEvent

AstralVote

VoteQuestGoal

ScrollCompletedEvent

AstralScrolls

ScrollCompleteQuestGoal

TowerFinishedEvent

AstralTower-Bridge

CompleteTowerGoal

AstralVote, AstralScrolls, and AstralTower-Bridge are hard dependencies of AstralFarmPass precisely because their events back these three quest goals — see Overview.

Cross-server messaging

FarmpassPacketRegistry (channel "farmpass") registers one packet, FarmpassUpdatedPacket (id 0x00, a single seasonId UUID field), on the MessagingService RabbitMQ connection created in AstralFarmPass#onEnable.

SeasonRotationService.createNewSeason() broadcasts the packet after every season creation or reroll:

this.plugin.messaging().send(FarmpassPacketRegistry.CHANNEL, new FarmpassUpdatedPacket(season.uniqueId()));

SeasonService's constructor registers the corresponding exchange listener: on non-master nodes, receiving a FarmpassUpdatedPacket re-reads the current season from the database and updates the local SeasonService cache (the master ignores its own broadcast, since it already holds the freshly-created season). This is the only thing the packet does — per-player quest progress and points are not carried over messaging; they replicate independently through AstralSync's own cross-server sync of FarmPassData. See Overview § Cross-server model for the master/non-master rotation rules this packet supports.

Last modified: 25 July 2026