Skip to content

Bounty Boards

Bounty boards display a rotating selection of tasks from a pre-defined pool. Think of them as a bulletin board in a town square — every day (or hour, or week, or season), the available quests change. Players pick from whatever’s currently posted.

They’re accessed via commands, so you can hook them up to NPCs, signs, GUI buttons, or anything else your server supports.

Boards existed pre-Beta 2   Beta 2 added wall-clock modes, streaks, and seasonal windows

Four rotation modes

INTERVAL (tick-based, legacy) plus DAILY, WEEKLY, MONTHLY wall-clock modes with per-board reset overrides.

Streak system

Track consecutive fully-cleared rotations per player and run milestone rewards via streakRewards.

Seasonal windows

activeFrom / activeUntil in MM-DD form — boards go dormant outside their season, including year-wrap ranges like November -> February.

Molang queries

q.player.bounty_streak, bounty_best_streak, bounty_completed_count, has_completed_bounty_task. Wire them into NPC dialogue or task filters.


File: config/journey/bounty_boards/town_board.json

{
"id": "town_board",
"name": "<gold>Town Bulletin Board",
"description": [
"<gray>Daily tasks from the townspeople",
"<gray>Refreshes every 24 hours"
],
"displayedTasks": 3,
"rotationInterval": 1728000,
"taskPool": [
"journey:catch_any_pokemon",
"journey:harvest_berries",
"journey:win_wild_battles",
"journey:level_up_pokemon",
"journey:heal_pokemon",
"journey:explore_zones"
]
}

This board has 6 tasks in its pool but only shows 3 at a time. Every 24 Minecraft hours (1728000 ticks), it randomly selects 3 new tasks from the pool.

That’s the classic INTERVAL shape. Below, we’ll layer in wall-clock rotation, streaks, weighted task pools, and seasonal gating.


FieldTypeDefaultDescription
idStringrequiredUnique identifier for the board
nameStringrequiredDisplay name (supports MiniMessage)
descriptionString[][]Description lines for the board GUI
displayedTasksNumber5How many tasks to show at once
rotationIntervalNumber24000Ticks between rotations — only honoured in INTERVAL mode. -1 = never rotate.
rotationSeedNumbernullIf set, the same rotation window always picks the same task set
lastRotationNumberautoLast rotation timestamp (auto-managed, don’t edit)
taskPoolTaskPoolEntry[]requiredMixed string / object pool entries — see Task Pool
requiredPermissionStringnullLuckPerms node required to open the board and receive rotation notifications
rotationModeEnum"INTERVAL"Beta 2 INTERVAL, DAILY, WEEKLY, or MONTHLY
customResetTimeStringnullBeta 2 HH:mm override for wall-clock modes
customResetDayStringnullBeta 2 Weekday name override for WEEKLY
customResetDayOfMonthNumbernullBeta 2 Day-of-month override for MONTHLY
trackStreaksBooleanfalseBeta 2 Enables per-player streak bookkeeping
streakRewardsObject{}Beta 2 Milestone rewards keyed by streak count
activeFromStringnullBeta 2 MM-DD start of seasonal window
activeUntilStringnullBeta 2 MM-DD end of seasonal window

taskPool accepts three JSON shapes. You can mix them freely within a single pool:

"taskPool": [
"journey:simple_task", // string -- weight defaults to 1.0
{ "taskId": "journey:weighted", "weight": 2.5 }, // weighted
{ "taskId": "journey:gated", "unlockCondition": "q.player.level > 10" },
{ "taskId": "journey:vip_only",
"weight": 0.5,
"unlockCondition": "q.player.has_flag('vip')" }
]
FieldNotes
taskIdnamespace:path of the task. Required.
weightDouble, default 1.0. Entries with weight <= 0 fall back to uniform selection from the remaining pool.
unlockConditionMolang expression. An entry is included if any online player passes the condition. Empty server falls back to unconditional entries only.

New in Beta 2
  • INTERVAL — tick-based. Rotates after rotationInterval ticks have elapsed. Default and legacy-compatible.
  • DAILY — rotates at a configured wall-clock time, once per day.
  • WEEKLY — rotates on a configured weekday at the configured time.
  • MONTHLY — rotates on a configured day-of-month at the configured time.

The classic shape. Rotates every Minecraft day (24000 ticks ≈ 20 real minutes):

{
"id": "town_board",
"name": "Town Bulletin Board",
"rotationInterval": 24000,
"displayedTasks": 5,
"taskPool": [ "journey:fetch_logs", "journey:slay_zombies" ]
}

Leave rotationMode off (or explicitly set "INTERVAL") and the board behaves exactly like it did before Beta 2.

Wall-clock modes read defaults from Journey’s main config:

{
"daily_reset_time": "00:00",
"weekly_reset_day": "Monday",
"monthly_reset_day": 1,
"reset_time_zone": "UTC"
}

Per-board overrides only replace matching fields. The timezone is global — there’s no per-board timezone override.


New in Beta 2

Set trackStreaks: true to enable per-player streak bookkeeping. A streak is the number of consecutive rotations during which the player cleared every displayed task on the board. Complete all of today’s bounties and your streak ticks up by one.

streakRewards is keyed by exact streak count. There’s no “every N” or “at least N” matcher.

"streakRewards": {
"3": ["command:give {player} minecraft:diamond 1"],
"5": "command:say {player} hit a 5-streak!",
"10": [
"command:eco give {player} 1000",
"script:q.player.notify('Legendary streak!')"
]
}

Reward strings are parsed by splitting on the first colon:

  • command:<value> — run as a command reward
  • script:<value> — run as a Molang script reward
  • Anything else (or no colon) — fallback to command

A streak resets to 0 when:

  • The previous rotation was empty, or
  • The player did not complete every previously-displayed task.

Offline players freeze in place — their window state and streak only reconcile the next time they’re online during a rotation.


New in Beta 2

Two MM-DD strings gate when the board rotates:

  • Both null → always active.
  • from <= until → active when now in [from, until].
  • from > until → year-wrap window. Active when now >= from || now <= until. Perfect for "11-01" -> "02-28" winter seasons.
  • Only one set → one-sided bound.
{
"id": "summer_festival",
"name": "<gold>Summer Festival Bounties",
"rotationMode": "WEEKLY",
"customResetDay": "Monday",
"activeFrom": "06-01",
"activeUntil": "08-31",
"displayedTasks": 5,
"trackStreaks": true,
"streakRewards": {
"4": ["command:lp user {player} permission set journey.title.summer_champ true"]
},
"taskPool": [
{ "taskId": "journey:beach_cleanup", "weight": 1.5 },
"journey:firework_run",
"journey:ice_delivery"
]
}

Outside June-August this board does not rotate, does not broadcast, and does not run streak resets.

{
"id": "winter_lodge",
"rotationMode": "MONTHLY",
"customResetDayOfMonth": 1,
"customResetTime": "00:00",
"activeFrom": "11-01",
"activeUntil": "02-28",
"displayedTasks": 4,
"taskPool": [
"journey:hunt_ice_pokemon",
"journey:gather_holly",
{ "taskId": "journey:legendary_blizzard", "weight": 0.1 }
]
}

Active November through February. December and January are both live.


New in Beta 2
QueryArgsReturns
q.player.bounty_streak(board_id)board idCurrent streak, or 0
q.player.bounty_best_streak(board_id)board idBest streak, or 0
q.player.bounty_completed_count(board_id)board idLifetime completions, or 0
q.player.has_completed_bounty_task(board_id, task_id)board id, task id1.0 if completed in the current rotation window

has_completed_bounty_task answers “completed since the last rotation”, not “completed ever” — the tracking set is wiped each rotation.


Boards are opened via commands. You choose how players trigger them.

/journey bountyboard town_board
q.player.execute_command('/journey bountyboard town_board');

You can have a task reward open a bounty board:

{
"type": "script",
"data": {
"scripts": [
"q.player.execute_command('/journey bountyboard town_board');"
]
}
}

CommandPermissionDescription
/journey bountyboard <board_id>journey.command.bountyboardOpen a bounty board
/journey bountyboard listjourney.command.bountyboard.listList all registered boards
/journey bountyboard rotate <board_id>journey.command.adminForce an immediate rotation
/journey createcontractjourney.command.createcontractOpen custom bounty creator

Tab-completion draws from all registered boards, regardless of permission.


Bounty board configs go in config/journey/bounty_boards/:

config/journey/bounty_boards/
├── town_board.json
├── daily_hunts.json
├── summer_festival.json
└── winter_lodge.json

If the directory doesn’t exist at startup, a single legacy example (town_board.json) is generated. Existing servers upgrading to Beta 2 keep their old files unchanged.


  • Two boards containing the same task id — the first match wins when Journey looks up the owning board, so a streak completion may be credited to the wrong board. Keep task ids unique across boards.
  • unlockCondition with an empty server — conditional entries are skipped and only unconditional tasks are rolled when no players are online during the rotation tick.
  • DST spring-forward — day additions are exactly 24 hours, so one rotation a year may land an hour off on a wall-clock day.
  • February is clamped to 28 days. customResetDayOfMonth: 29 becomes 28 even in a leap year.
  • Streak rewards are exact-match. streakRewards["3"] does not fire on streak 4.

  • Variety matters. Include different task types (combat, exploration, collection) in each pool.
  • Match difficulty to rotation speed. Fast-rotating boards should have easy tasks; weekly boards can afford longer commitments.
  • Test task availability. Make sure all tasks in the pool exist and work correctly — broken references silently disappear from rotations.
  • Use weighted entries for rare flavors. A 0.1 weight on a legendary hunt makes it show up once in a while without dominating the pool.
  • Keep streak milestones sparse. 3, 7, 14, 30 reads as “earned” — every single number reads as “farm grind”.
  • Pair seasonal boards with faction visibility conditions. A winter board with activeFrom: "11-01" pairs naturally with a winter-themed faction gated behind the same condition.