Cobblemon Economy is a server-focused economy and shop system for Cobblemon on Fabric. It provides NPC shops, dual currencies, persistent storage, and reward hooks for captures and battles.
Website: https://ryvexam.fr
Developer documentation:
- Architecture and runtime flows
- Contributor and operator workflow
- Technical audit and follow-up priorities
The development build targets Cobblemon 1.8.1 on Fabric 1.21.1. Capture and
Pokédex reward handling remains compatible with Cobblemon 1.7.x: the former
CAUGHT progression name and the 1.8+ OWNED name are normalized internally.
Install the matching Fabric Loader/API required by the Cobblemon version on the
server; the economy mod itself does not require a separate 1.7 or 1.8 build.
Each JAR below was tested individually on a dedicated Fabric server with
Minecraft 1.21.1, Fabric Loader 0.19.5, Fabric API 0.116.17+1.21.1,
Cobblemon 1.8.1+1.21.1, and Java 21. The JARs were never installed side by
side.
| Version | Start | Mod init | /eco reload |
Shops | Quests | Clean stop |
|---|---|---|---|---|---|---|
0.0.13 |
✅ | ✅ | ✅ | ✅ | N/A | ✅ |
0.0.14 |
✅ | ✅ | ✅ | ✅ | N/A | ✅ |
0.0.15 |
✅ | ✅ | ✅ | ✅ | N/A | ✅ |
0.0.16 |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
0.0.17 |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
0.0.18 |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
The complete migration sequence 0.0.13 → 0.0.14 → 0.0.15 → 0.0.16 → 0.0.17 → 0.0.18 was also tested. Legacy balances, PCO, purchase/sell limits,
capture counts, quest state, and quest progress were preserved. See the full
version and migration report.
Tagged releases are built and published by Forgejo CI only. Push a version tag
such as v0.0.18 to run the Java 21 build and publish the JAR to Modrinth and
CurseForge; credentials are stored only as Forgejo repository secrets.
- Dual currencies: PokeDollars and PCO
- NPC shopkeepers with GUI-based shops (buy and sell)
- Per-world config and SQLite storage (config, DB, logs, skins)
- Dynamic quantity selection in shops (middle click)
- Item definitions support component syntax (enchantments, datapack items)
- Loot crates via dropTable or Minecraft loot tables
- Command execution items - sell commands instead of items (e.g., crate keys, effects, shoutouts)
- Purchase limits per item with optional cooldowns
- Auto-downloaded shopkeeper skins from server
- Transaction logging to file
- Capture, discovery, and battle rewards with multipliers
- Raid den battle rewards (separate configurable PokeDollar amount)
- Fossil revival rewards for shiny/radiant/legendary/paradox Pokémon
- Quest board interface (client custom screen, non-SGUI)
- Optional integrations: YAWP protection flag and Star Academy grading
- Optional CobbleDollars and Impactor bridge compatibility
Player:
/balor/balance/pco/pay <player> <amount>
Admin (permission level 2):
/eco reload/eco shop list/eco shop get <id>/eco quest list/eco questnpc list/eco questnpc get <id>/eco questboard list/eco questboard open <id>/eco questboard bind <id>/eco questboard unbind/eco skin <name>/eco item/balance <player> <add|remove|set> <amount>/pco <player> <add|remove|set> <amount>
All server data is stored under world/config/cobblemon-economy/. The JSON
files are intentionally separated by responsibility:
| File | Contents | How to edit |
|---|---|---|
config.json |
Global economy settings: currency backend, starting balances, rewards, multipliers, profiling, and legacy inline shop fallback | Edit global economy values here |
shops.json |
Shop IDs, NPC display settings, buy/sell modes, item entries, limits, loot tables, and command rewards | Edit shops here; this is the authoritative shop file |
milestone.json |
Unique-capture milestone thresholds and rewards | Edit milestone rewards here |
quests.json |
Quest IDs, objectives, prerequisites, rotation rules, and rewards | Edit quest definitions here |
quest_npcs.json |
Quest NPC/board IDs, dialogue, quest pools, rotations, skins, and active-quest limits | Edit which NPC/board offers which quests here |
quest_boards_bindings.json |
Dimension and block-position bindings to quest board IDs | Prefer /eco questboard bind and /eco questboard unbind |
shops.json is the preferred source for shops. Existing servers remain
backward-compatible: if it is missing, the mod reads the legacy shops object
inside config.json, writes a new shops.json, and keeps the legacy data
readable for older JARs. If both are present and shops.json contains shops,
it is authoritative; a missing or empty separate file can still fall back to
the legacy inline object. Shop, item, quest, NPC, and board IDs are persistent
identifiers and must not be renamed without an explicit migration.
After editing JSON, run /eco reload. The reload validates and rewrites only
the affected JSON files; it does not reset SQLite balances or quest progress.
Current writes are atomic, create .bak backups, and quarantine malformed files
as .broken-<timestamp> before defaults are regenerated.
Persistence compatibility:
- JSON files accept the legacy field names and contain a
configVersionwhen written by current versions. - Existing
economy.dbandquests.dbfiles are migrated additively with a.bakbackup before upgrade. - Invalid JSON is preserved as a timestamped
.broken-*file before defaults are regenerated. - Keep shop, item, quest, NPC, and board IDs stable because they are persistent references.
- See
docs/persistence-compatibility.mdfor upgrade, downgrade, and recovery procedures. - See
docs/version-compatibility.mdfor the tested0.0.13→ current migration matrix.
Milestone rules:
- File missing: defaults are generated.
- Empty file: defaults are used and saved.
- Keys are unique-capture counts (strings), values are rewards in PokeDollars.
Global settings:
main_currency(cobeco,cobbledollars,impactor; defaultcobeco)startingBalancestartingPcobattleVictoryRewardraidDenVictoryReward(defaults tobattleVictoryRewardif missing)capture_event_base_reward(defaults tobattleVictoryRewardif missing; reward for a first valid capture, and reused as the base for fossil revive special payouts)capture_multi_reward(defaults to0; reward for repeat non-special captures of an already known species)- Special captures (
shiny,radiant,legendary/mythical,paradox) still usecapture_event_base_rewardmultiplied by their configured special multiplier. battleVictoryPcoRewardbattleTowerCompletionPcoBonus(small extra PCO reward on Battle Tower wins)capture_shiny_multipliercapture_radiant_multipliercapture_legendary_multipliercapture_paradox_multiplierenableProfiling(logs slow operations when true)profilingThresholdMs(minimum ms to log)
Legacy compatibility:
- Old keys like
captureReward,capture_reward,newDiscoveryReward,special_capture_reward_ignores_pokedex_history,normal_capture_reward_requires_new_pokedex_entry,shinyMultiplier,radiantMultiplier,legendaryMultiplier, andparadoxMultiplierare still detected for migration. - On the next config rewrite, Cobblemon Economy saves
capture_event_base_reward,capture_multi_reward, and the explicit multiplier key names above.
Shop definition fields:
titlecurrency(POKEorPCO)skin(filename without extension)skinModel(optional:steveoralex, defaultsteve)isSellShop(true to let players sell)linkedShop(optional)linkedShopIcon(optional item id)items
Item definition fields:
type-"item"(default) or"command"id- Required stable identifier for every entry- for
type: "item": use the real item ID - for
type: "command": use an internal key such asserver:vote_key
- for
name- Display namepricenbt(legacy NBT string)dropTable(array of item ids)lootTable(minecraft loot table id)components(data components for items, written as normal JSON)enchantments(shorthand, no escaping needed)lore(shorthand, list of lines)unbreakable(shorthand, boolean)customModelData(shorthand, integer)glint(shorthand, boolean fake enchant shine)buyLimit(optional)buyCooldownMinutes(optional, 0 means lifetime limit)sellLimit(optional)sellCooldownMinutes(optional, 0 means lifetime limit)command- Command string fortype: "command"(use%player%placeholder)displayItem- Custom display configuration for command items:material- Item ID to displaydisplayname- Custom name shown in shopenchantEffect- Boolean for enchantment glint
Item limit rules:
- Missing
buyLimitorbuyLimit <= 0: unlimited. buyLimit > 0and missingbuyCooldownMinutes: lifetime limit.buyLimit > 0andbuyCooldownMinutes = 0: lifetime limit.buyLimit > 0andbuyCooldownMinutes > 0: limit resets every N minutes.- Missing
sellLimitorsellLimit <= 0: unlimited. sellLimit > 0and missingsellCooldownMinutes: lifetime limit.sellLimit > 0andsellCooldownMinutes = 0: lifetime limit.sellLimit > 0andsellCooldownMinutes > 0: limit resets every N minutes.
Example shop:
{
"shops": {
"my_shop": {
"title": "My Shop",
"currency": "POKE",
"items": [
{ "id": "minecraft:diamond", "name": "Diamond", "price": 1000 },
{ "id": "minecraft:diamond_sword[minecraft:enchantments={levels:{'minecraft:sharpness':5}}]", "name": "Sharpness V", "price": 5000 },
{ "id": "academy:booster_pack[academy:booster_pack='base']", "name": "Booster Pack", "price": 100 },
{ "id": "minecraft:chest", "name": "Mystery Box", "price": 500, "dropTable": ["minecraft:diamond", "cobblemon:rare_candy"] },
{ "id": "minecraft:chest", "name": "Dungeon Loot", "price": 1000, "lootTable": "minecraft:chests/simple_dungeon" },
{ "id": "cobblemon:rare_candy", "name": "Rare Candy", "price": 50, "buyLimit": 3, "buyCooldownMinutes": 1200 }
]
}
}
}Item limit only:
{ "id": "cobblemon:rare_candy", "name": "Rare Candy", "price": 50, "buyLimit": 3, "buyCooldownMinutes": 1200 }You never have to write escaped JSON like "{\"levels\":{...}}". Pick whichever style you prefer.
1. Shorthand fields (simplest):
{
"id": "minecraft:diamond_sword",
"name": "Champion Blade",
"price": 5000,
"enchantments": { "sharpness": 5, "unbreaking": 3, "mending": 1 },
"lore": ["Reward of the Champion", "Untradeable"],
"unbreakable": true
}enchantments also accepts a list: ["sharpness 5", "unbreaking 3"].
Names without a namespace are treated as minecraft:, and a missing level means 1.
2. Real JSON in components (full control, any component):
{
"id": "minecraft:diamond_sword",
"name": "Sharpness V",
"price": 5000,
"components": {
"minecraft:enchantments": { "levels": { "minecraft:sharpness": 5 } },
"minecraft:custom_name": { "text": "Sharpness V", "color": "gold" },
"academy:booster_pack": "base"
}
}3. Vanilla /give syntax on the id (use single quotes so JSON stays clean):
{ "id": "minecraft:diamond_sword[minecraft:enchantments={levels:{'minecraft:sharpness':5}}]", "name": "Sharpness V", "price": 5000 }Notes:
- Component names without a namespace are treated as
minecraft:. - If the same component is set in several ways,
componentswins over the shorthand fields, which win over the inline id syntax. - Old configs using escaped strings (
"minecraft:enchantments": "{\"levels\":{\"minecraft:sharpness\":5}}") keep working. - The shorthands also work on
type: "command"entries to decorate the GUI icon.
Command execution item (sells a command instead of an item):
{
"id": "server:vote_key",
"type": "command",
"command": "crate key give vote 1 %player%",
"price": 100,
"buyLimit": 1,
"buyCooldownMinutes": 1440,
"displayItem": {
"material": "supplementaries:key",
"displayname": "Vote Crate Key",
"enchantEffect": true
}
}Command item note:
idis still mandatory for command entries because the config loader and limit tracking use it as the entry key.
Mixed shop example (items + loot tables + commands):
{
"shops": {
"mixed_shop": {
"title": "MIXED SHOP",
"currency": "POKE",
"items": [
{ "id": "cobblemon:poke_ball", "name": "Poké Ball", "price": 200 },
{ "id": "minecraft:chest", "name": "Dungeon Loot", "price": 1000, "lootTable": "minecraft:chests/simple_dungeon" },
{
"type": "command",
"id": "server:regeneration_potion",
"command": "effect give %player% minecraft:regeneration 300 1",
"price": 300,
"displayItem": {
"material": "minecraft:potion",
"displayname": "Regeneration Potion (5min)",
"enchantEffect": true
}
}
]
}
}
}Quest configuration is split into definitions, boards/NPCs, and persistent player state:
| File | Purpose |
|---|---|
quests.json |
Quest IDs, objectives, prerequisites, repeat rules, timers, and rewards |
quest_npcs.json |
Board/NPC IDs, quest pools, rotation, visible slots, skins, dialogue, and active-quest limits |
quest_boards_bindings.json |
Dimension/block-position bindings; manage with /eco questboard bind and /eco questboard unbind |
quests.db |
Player quest state and objective progress; do not edit manually |
Quest lifecycle: a player opens a board, accepts a quest from its current
rotation, completes objectives from game events, then claims the rewards.
Supported objective types are capture, fossil_revive, battle_win,
raid_win, and tower_win.
Example quests.json:
{
"quests": {
"catch_shiny_gyarados": {
"name": "Red Leviator",
"repeatPolicy": "DAILY",
"repeatable": true,
"timeLimitMinutes": 1440,
"requiresCompleted": [],
"objectives": [
{
"type": "capture",
"count": 1,
"species": ["cobblemon:gyarados"],
"shiny": true
}
],
"rewards": {
"pokedollars": 15000,
"pco": 120,
"commands": []
}
}
}
}Quest fields and rules:
repeatPolicy:DAILYresets at the next board rotation,ALWAYSallows replay after claiming, andONCEpermanently locks the quest after claiming.repeatable: falsemakes a quest one-time after claiming.timeLimitMinutescancels an expired active quest and clears its progress.cooldownMinutesadds a personal cooldown after claiming.requiresCompletedaccepts prerequisite quest IDs from the same board/NPC; cross-board prerequisites are not supported.rewardscan combinepokedollars,pco, and servercommands. Commands run with server permission level 4 and support the%player%placeholder.- Capture filters include
species,types,labels,pokeball,dimension/dimensions, andshiny. Values in one filter are OR-matched; different filters are AND-matched. Battle, raid, and tower objectives usecount.
Example quest_npcs.json:
{
"quest_npcs": {
"safari_guide": {
"displayName": "Safari Guide",
"skin": "shopkeeper",
"skinModel": "alex",
"maxActive": 2,
"visibleQuests": 6,
"sharedRotation": true,
"rotationMode": "MIDNIGHT",
"questPool": ["catch_shiny_gyarados"]
}
}
}questPool contains quest IDs from quests.json. sharedRotation: true uses
the same selection for every player; false selects per player. Use the quest
NPC commands to assign profiles, and /eco reload after editing JSON. Quest
IDs, NPC IDs, and board bindings are persistent references and must not be
renamed silently.
- Place PNGs in
world/config/cobblemon-economy/skins/. - Use
/eco skin <name>to get a Skin Setter. - In
shops.jsonandquest_npcs.json, you can setskinModeltosteveoralexfor arm model type.
- Config:
world/config/cobblemon-economy/config.json - Shops:
world/config/cobblemon-economy/shops.json - Milestones:
world/config/cobblemon-economy/milestone.json - Quests:
world/config/cobblemon-economy/quests.json - Quest NPCs:
world/config/cobblemon-economy/quest_npcs.json - Quest board bindings:
world/config/cobblemon-economy/quest_boards_bindings.json - Database:
world/config/cobblemon-economy/economy.db - Quest database:
world/config/cobblemon-economy/quests.db - Transactions:
world/config/cobblemon-economy/transactions.log - Skins:
world/config/cobblemon-economy/skins/
For the full upgrade, downgrade, and recovery procedure, see
docs/persistence-compatibility.md.
- Cobblemon (required): capture, pokedex, battle victory, and fossil events come from Cobblemon's event bus.
- Cobblemon Raid Dens (optional): raid completion is read from
com.necro.raid.dens.common.events.RaidEvents(RAID_BATTLE_START/RAID_END). - Battle Tower-like systems (optional): no hard dependency; tower wins are detected from battle actor metadata, entity IDs/tags, and explicit
tour_de_combatNPC tag. - YAWP: flag
melee-npc-cobecocontrols shopkeeper vulnerability. - Star Academy: optional grading integration when the
academymod is present. - CobbleDollars: optional compatibility bridge support.
- Impactor: optional compatibility bridge support.
Quest event notes:
raid_winobjectives use Raid DensRAID_ENDwhen the Raid Dens mod is available, with fallback detection from Cobblemon battle events when it is not.tower_winobjectives rely on tower actor detection; for custom NPC towers, tag NPCs withtour_de_combat(Tower Tagger from/eco item) to guarantee detection.
Currency backend behavior (main_currency):
cobeco: Cobblemon Economy database is authoritative for PokeDollars.cobecomirrors balances to CobbleDollars and Impactor accounts when those mods are installed, and bridges their API transactions into CobEco.cobbledollars: Cobblemon Economybalanceoperations use CobbleDollars player balance (online players).impactor: Cobblemon Economybalanceoperations use Impactor primary account balance.
If placeholder-api is installed, Cobblemon Economy exposes balance placeholders for tablists/scoreboards.
Use the placeholder format required by your tablist plugin (often %namespace:placeholder% or {namespace:placeholder}).
Available placeholders (recommended namespace: cobeco):
cobeco:balancecobeco:balance_symbolcobeco:pcocobeco:pco_symbol
Only the cobeco namespace is registered by the current Placeholder API
integration.
./gradlew buildJar output: build/libs/ (use the remapped jar).
For the complete development checklist, see CONTRIBUTING.md.
Discord: https://discord.gg/zxZXcaTHwe
Thanks to Rikunji for their contributions to the project.