Skip to content

Achievements

Achievements are Journey’s milestone tracker. Each achievement is a self-contained JSON file with an icon, a category, a point value, and a reward list. Players see their unlocked achievements in the Content Book’s Achievements tab.

New in Beta 2

Flat unlocks

One condition, one reward list. Perfect for one-shot milestones like “first catch” or “beat the Elite Four”.

Progressive ladders

Named tiers with their own thresholds, ids, and point values. Great for counters: “win 10 / 50 / 200 / 500 battles”.

Hidden spoilers

hidden: true keeps an achievement out of the Content Book list until the player unlocks it. The unlock toast still fires.

Script-grantable

Cutscenes, timelines, and scripts can grant achievements directly without evaluating a criteria expression.


Here’s the simplest possible achievement — unlock “Gotta Start Somewhere” when a player catches their first Pokemon:

File: config/journey/achievements/first_catch.json

{
"id": "first_catch",
"name": "<yellow>Gotta Start Somewhere",
"description": "Catch your very first Pokemon.",
"icon": "cobblemon:poke_ball",
"category": "POKEMON",
"criteria": "catch_pokemon",
"hidden": false,
"points": 10,
"rewards": [
{ "type": "command", "data": { "command": "give {player} cobblemon:poke_ball 5" } }
]
}

That’s it. When the criteria resolves true, the player unlocks the achievement, earns 10 points, receives five Poke Balls, and sees a toast. Files are loaded recursively from config/journey/achievements/, keyed by the id field — so you can organize them into subdirectories however you like.


Achievements come in two shapes. Pick the one that fits your milestone.

A flat achievement has one unlock condition and one reward list. Use it for one-shot events that either fire or don’t: “beat the first gym,” “catch your starter,” “complete the tutorial.”

{
"id": "first_shiny",
"name": "<light_purple>A Different Sparkle",
"description": "Catch a shiny Pokemon in the wild.",
"icon": "cobblemon:shiny_stone",
"category": "POKEMON",
"criteria": "q.player.has_flag('journey:shiny_caught')",
"hidden": true,
"points": 50,
"rewards": [
{ "type": "currency", "data": { "currency": "impactor:pokedollars", "amount": 10000 } }
]
}

The criteria field is a Molang expression. When it resolves truthy, the achievement unlocks.


FieldTypeDefaultDescription
idStringRequiredRegistry key. Files with an empty id are skipped.
nameString""Display name. Supports MiniMessage.
descriptionString or String[][]Single strings auto-wrap into a one-element list.
iconStringminecraft:paperVanilla or modded item id.
categoryEnumGENERALOne of GENERAL, EXPLORATION, COMBAT, COLLECTION, STORY, POKEMON, SOCIAL.
criteriaMolang or Condition DSL""Flat achievements only. Empty = grantable only by scripts / cutscenes.
rewardsReward[][]Polymorphic — same reward types as tasks.
hiddenBooleanfalseHides from the Content Book list until unlocked. Unlock toasts still fire.
pointsInt0Added to the player’s lifetime achievement points.
progressiveTier[]nullWhen set, turns the achievement into a ladder.
progress_sourceStringnullRequired for progressive achievements. Drives tier progress.
FieldNotes
targetThreshold compared against the player’s current progress count.
idIndependently tracked as an unlock. Convention: <parent_id>_<target>.
nameToast title for the tier.
pointsAwarded when this tier unlocks.
ValueCounts
"pokedex_caught"Species in the player’s Pokedex with CAUGHT knowledge.
Any other string XPlayer flags that equal X or start with X:.

A flag-prefix source expects your content to write matching flag entries — via task rewards, cutscene hooks, or scripts.


Achievements can be awarded two ways:

  • Criteria-driven — you set a Molang expression or a flag-prefix counter and Journey unlocks the achievement when the condition is met. Works for both flat and progressive achievements.
  • Direct grant — cutscenes, timelines, and scripts can grant an achievement by id. This bypasses the criteria check entirely and just runs the reward chain.

Achievement rewards use the same reward types and behavior as the task system. Any reward kind valid in a task config is valid here: command, currency, item, script, timeline, buff, reputation.

"rewards": [
{ "type": "command", "data": { "command": "give {player} cobblemon:rare_candy 3" } },
{ "type": "currency", "data": { "currency": "impactor:pokedollars", "amount": 1000 } },
{ "type": "script", "data": { "scripts": ["q.player.tell_minimessage('<gold>Legendary!');"] } }
]

QueryReturns
query.has_achievement(id)1.0 if unlocked, else 0.0
query.achievement_points()Current lifetime achievement points
query.has_discovery(zoneId)1.0 if the zone has been discovered
query.discovery_count()Number of zones the player has discovered

Ten default definitions ship with Journey and are copied into config/journey/achievements/ on first server start:

FileCategoryType
first_catch.jsonPOKEMONflat
first_evolution.jsonPOKEMONflat
hatch_first_egg.jsonPOKEMONflat
shiny_encounter.jsonPOKEMONflat, hidden
legendary_hunter.jsonPOKEMONflat, hidden
pokemon_collector.jsonCOLLECTIONprogressive (4 tiers)
battle_victories.jsonCOMBATprogressive (4 tiers)
world_explorer.jsonEXPLORATIONprogressive (3 tiers)
quest_completionist.jsonGENERALprogressive (3 tiers)
social_butterfly.jsonSOCIALprogressive (3 tiers)

  • Criteria are Molang expressions, not event names. A bare identifier like catch_pokemon resolves against Molang queries — it won’t fire unless you’ve wired a matching query. Use real expressions (q.player.has_flag('journey:shiny_caught')) or the Condition DSL form.
  • Progressive achievements ignore criteria. Only progress_source + tier target values drive unlocks.
  • Tier ids are global. Namespace them with the parent id to avoid collisions across your pack.
  • No rollback. Lowering the underlying counter doesn’t un-unlock a tier.
  • Hidden achievements still toast. hidden: true only suppresses the Content Book listing before unlock.

There are no dedicated /journey achievement subcommands. To grant an achievement from chat, use a command reward that writes a player flag, or grant it from a cutscene, timeline, or script.


  • Use progressive ladders for counters. Flag-prefix sources scale cleanly — you don’t have to write a criteria expression per tier.
  • Namespace tier ids as <parent>_<target> to keep them unique across your pack.
  • Use hidden: true for spoilers. Shiny, legendary, and story milestones benefit from hiding the listing until earned.
  • Ship with a journey: flag namespace so progressive sources don’t collide with other packs.