Four rotation modes
INTERVAL (tick-based, legacy) plus DAILY, WEEKLY, MONTHLY wall-clock modes with per-board reset overrides.
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.
| Field | Type | Default | Description |
|---|---|---|---|
id | String | required | Unique identifier for the board |
name | String | required | Display name (supports MiniMessage) |
description | String[] | [] | Description lines for the board GUI |
displayedTasks | Number | 5 | How many tasks to show at once |
rotationInterval | Number | 24000 | Ticks between rotations — only honoured in INTERVAL mode. -1 = never rotate. |
rotationSeed | Number | null | If set, the same rotation window always picks the same task set |
lastRotation | Number | auto | Last rotation timestamp (auto-managed, don’t edit) |
taskPool | TaskPoolEntry[] | required | Mixed string / object pool entries — see Task Pool |
requiredPermission | String | null | LuckPerms node required to open the board and receive rotation notifications |
rotationMode | Enum | "INTERVAL" | Beta 2 INTERVAL, DAILY, WEEKLY, or MONTHLY |
customResetTime | String | null | Beta 2 HH:mm override for wall-clock modes |
customResetDay | String | null | Beta 2 Weekday name override for WEEKLY |
customResetDayOfMonth | Number | null | Beta 2 Day-of-month override for MONTHLY |
trackStreaks | Boolean | false | Beta 2 Enables per-player streak bookkeeping |
streakRewards | Object | {} | Beta 2 Milestone rewards keyed by streak count |
activeFrom | String | null | Beta 2 MM-DD start of seasonal window |
activeUntil | String | null | Beta 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')" }]| Field | Notes |
|---|---|
taskId | namespace:path of the task. Required. |
weight | Double, default 1.0. Entries with weight <= 0 fall back to uniform selection from the remaining pool. |
unlockCondition | Molang expression. An entry is included if any online player passes the condition. Empty server falls back to unconditional entries only. |
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.
Resets every wall-clock day at the configured reset time, in the server’s configured timezone:
{ "id": "daily_hunts", "name": "Daily Pokemon Hunts", "rotationMode": "DAILY", "customResetTime": "06:00", "displayedTasks": 3, "taskPool": [ "journey:catch_grass", "journey:catch_water" ]}customResetTime overrides the global daily reset time for this board only.
Resets every Wednesday at 18:00:
{ "id": "weekly_guild", "name": "Guild Quests", "rotationMode": "WEEKLY", "customResetDay": "Wednesday", "customResetTime": "18:00", "displayedTasks": 5, "taskPool": [ "journey:beach_cleanup", "journey:firework_run" ]}Weekday names are case-insensitive (Monday, tuesday, etc.).
Resets on the 15th at midnight:
{ "id": "monthly_challenge", "name": "Monthly Challenge", "rotationMode": "MONTHLY", "customResetDayOfMonth": 15, "customResetTime": "00:00", "displayedTasks": 4, "taskPool": [ "journey:elite_trial", "journey:legendary_hunt" ]}Day-of-month is coerced to the month’s length — 31 on a 30-day month becomes 30.
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.
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 rewardscript:<value> — run as a Molang script rewardA streak resets to 0 when:
Offline players freeze in place — their window state and streak only reconcile the next time they’re online during a rotation.
Two MM-DD strings gate when the board rotates:
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.{ "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.
| Query | Args | Returns |
|---|---|---|
q.player.bounty_streak(board_id) | board id | Current streak, or 0 |
q.player.bounty_best_streak(board_id) | board id | Best streak, or 0 |
q.player.bounty_completed_count(board_id) | board id | Lifetime completions, or 0 |
q.player.has_completed_bounty_task(board_id, task_id) | board id, task id | 1.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_boardq.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');" ] }}| Command | Permission | Description |
|---|---|---|
/journey bountyboard <board_id> | journey.command.bountyboard | Open a bounty board |
/journey bountyboard list | journey.command.bountyboard.list | List all registered boards |
/journey bountyboard rotate <board_id> | journey.command.admin | Force an immediate rotation |
/journey createcontract | journey.command.createcontract | Open 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.jsonIf 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.
unlockCondition with an empty server — conditional entries are skipped and only unconditional tasks are rolled when no players are online during the rotation tick.customResetDayOfMonth: 29 becomes 28 even in a leap year.streakRewards["3"] does not fire on streak 4.0.1 weight on a legendary hunt makes it show up once in a while without dominating the pool.3, 7, 14, 30 reads as “earned” — every single number reads as “farm grind”.activeFrom: "11-01" pairs naturally with a winter-themed faction gated behind the same condition.