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
Repository: https://maven.astralrealms.fr/repository/maven-public/.
Plugin services
Getter | Type | Use |
|---|---|---|
|
| Advance quest progress, look up a player's current quest instances, pull cached quests for a season. |
|
| Look up the current season (cached or from the database). |
|
| Force-create or re-roll the current season. |
|
| Look up loaded quest blueprints by id or category. |
|
| Look up the |
|
| Open the |
QuestService
Method | Returns | Use |
|---|---|---|
|
| Advances every unlocked, unexpired, non-completed quest instance whose blueprint's goal is an instance of |
|
| The player's |
|
| Quests currently held in the in-memory Caffeine cache for a season. |
|
| Loads (and caches) every quest row for a season from the database. |
|
| Direct database access, used internally by |
tryProgress is the actual extension point — see Extending the quest goal framework below.
SeasonService
Method | Returns | Use |
|---|---|---|
|
| Cached current season if not expired, otherwise re-queries the database. |
|
| Shortcut for the cached season's id, or |
|
| The raw cached value (no expiry check, no I/O) — |
|
| Overwrites the cache and, if the season hasn't already ended, preloads its quests via |
SeasonRotationService
Method | Returns | Use |
|---|---|---|
|
| Creates a |
|
| Deletes every quest and the season row for the current season, clears blueprint-rotation tracking, then calls |
BlueprintService
Method | Returns | Use |
|---|---|---|
|
| Resolve a loaded blueprint by its |
|
| Every loaded blueprint. |
|
| Blueprints whose |
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 |
|---|---|---|
|
| The player who shot the arrow. |
|
| The arrow that came to rest (never a |
|
| Per-entity-type kill counts credited to that single arrow. |
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>:
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:
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 (alongsidetype:) —QuestBlueprintTypeSerializerdeserializes the wholegoalnode 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.Register the type name against the class:
QuestTypeRegistry.register("my-goal", MyGoal.class);QuestTypeRegistryis a static, name-keyed registry (NamedRegistry<Class<? extends QuestGoal<?>>>).QuestBlueprintTypeSerializerreads a blueprint'sgoal.typestring, resolves it throughQuestTypeRegistry.findByName, and fails blueprint loading with aSerializationExceptionif the name isn't registered — soregistermust run beforeBlueprintService.load()scansquests/*.yml. SinceAstralFarmPass#onEnableloads blueprints as part of its ownonEnable, a dependent plugin that only registers in its ownonEnablewill miss the initial load if itdepends on AstralFarmPass (Bukkit runs FarmPass'sonEnablefirst); register fromonLoad()instead, or register inonEnableand rely on the next/pass reloadto pick the type up.Drive progress for the goal's event.
GoalListeneronly wires the goal types AstralFarmPass ships itself — for a new goal type, register your ownListenerand callQuestService.tryProgressdirectly:@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")).
Member | Type | Description |
|---|---|---|
|
| The player's rolled quest instances for their current season. |
|
| Claimed reward keys, |
|
| The season this data belongs to; reset to |
|
| Current point balance (read via the Lombok-generated getter). |
|
| Mutates points through a function of the current value, e.g. |
|
| Clears quests, claimed rewards, and points, and nulls |
|
| Clears quests, claimed rewards, and points but leaves |
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 |
|---|---|---|
| AstralVote |
|
| AstralScrolls |
|
| AstralTower-Bridge |
|
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:
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.