Coins are data
Which items are money, and what they're worth, is the medici:coin_values data map:
data/medici/data_maps/item/coin_values.json in your datapack (a data map's files always sit in the map's own
namespace, medici, whatever namespace your pack uses; NeoForge merges the files of every pack). Medici's own:
{
"values": {
"medici:penny": { "value": 1, "symbol": "P" },
"medici:groat": { "value": 10, "symbol": "G" },
"medici:ducat": { "value": 100, "symbol": "D" }
}
}
Add a coin (any item, any value in pennies, a short symbol for prices) or change values in a datapack. Every
Medici block reads this map; change-making works with any set (a 25-penny coin is fine). Tags: #medici:coins,
#c:coins, #medici:coins/copper|silver|gold.
Turning sources down
All new money passes the decree (medici-server.toml, CONFIG). Fewer coins on your server: lower
targetPerPlayer. No mob coins: mobCoinsPerDay = 0.
Found money
- Mob drops:
data/<ns>/medici/mob_drops/*.json, first matching rule wins; files are read in order of their path, then their namespace (a.jsonbeforeb.json, whatever the namespace):{"entities": ["minecraft:pillager", "#c:bosses"], "chance": 0.6, "min": 1, "max": 3, "rare_value": 10, "rare_chance": 0.08, "antiques": 0, "antique_chance": 0}(pennies). Only real player kills of mobs that spawned naturally, from world generation, structures, patrols or raids pay; spawners, eggs, breeding and machines never do. Daily cap per player:mobCoinsPerDay. - Chest loot: the global loot modifier
medici:coins_in_chests(data/medici/loot_modifiers/coins_in_chests.json) holds rules{"tables": ["minecraft:chests/village/*", "yourmod:*"], "chance", "min", "max", "antiques", "antique_chance", "letter_chance"}. Override the file in a datapack to add your structures. - Antique coins: the
medici:antique_valuesdata map (item → pennies paid at a bank counter), indata/medici/data_maps/item/antique_values.json. - Appraisal:
data/<ns>/medici/appraisal/*.json,{"values": {"minecraft:bread": 0.5, "#c:ingots/iron": 0.6}}(pennies per item; files in order of their path, then namespace, later entries override earlier ones). The Merchant wishes for items priced 0.01 to 40 p: every item listed by id, plus every item in themedici:merchant_wishesitem tag that the appraisal prices at all (by id or through a tag entry). Tagged items come up 4 times as often. Add your mod's legal goods to both: a price here, an entry indata/medici/tags/item/merchant_wishes.json.
KubeJS
With KubeJS (2101.7+, as in ATM10 8.4) Medici adds three things to server scripts. Examples you can copy are in
examples/kubejs; CI loads every one of them on a test server.
Events: MediciEvents.<name>(event => …). They fire before anything moves; event.cancel() refuses the
action (the player sees "Not allowed here") and nothing changes.
| event | when | properties | cancel |
|---|---|---|---|
purchase |
a player pays at a shop, coin slot, turnstile or rental chest, or sells to a shop | player, pos, kind (sale, buy, coin_slot, turnstile, rental), item, price (pennies), ownerHouse |
yes |
mint |
a mint press or die press is about to strike | level, pos, house, result, value, source (mint, die_press) |
yes |
transfer |
a bank transfer | player, from, to, amount, fee |
yes |
ticket |
a valid ticket at a ticket gate | player, pos, venue, venueName, kind |
yes |
commission |
the Artist is about to take a paid commission | patron, commission (kind, tier, size, …), price (settable, pennies) |
yes |
merchantWishes |
the Merchant arrives with his wishes | level, tradingHouse, wishes (a list you can change), add(item, pennies), clear() |
no |
Event strings are Java strings: compare with String(event.kind) === 'sale'.
MediciDecree: supply(server) (coins in the world, pennies), houseLeft(player) (what their house may still
mint today), wallet(player) (coins they carry), format(pennies) (1D 4G 5P), and charge(player, pennies, reason): takes the coins (purses first, change given) and burns them through the mint ledger; false if they
can't pay. Scripts can burn but never mint: new coins only come from the decree.
Recipe schema: event.recipes.medici.minting(result, metal, die) with optional .energy(fe) and .time(ticks).
Other mods can use the same events in Java: de.benkl.medici.api.MediciEvent.* on NeoForge.EVENT_BUS.
| example | what it does |
|---|---|
medici.js |
a richer groat recipe, no dirt trade, a stamp duty on big transfers, Merchant wishes, a ride counter |
claim_charter.js |
/charter: one extra FTB Chunks claim for 2 ducats, burned |
waystone_toll.js |
opening a waystone costs 5 p |
quest_shop.js |
/shop <item>: quest helpers for coins |
exchange.js |
one-way exchange into Lightman's Currency or Numismatics coins, if the pack has them |
More (KubeJS events, recipe schemas) as the features land (#52).
Java API: MediciTreasury
For mods that pay or charge players money (traders, contracts, quest boards): de.benkl.medici.api.MediciTreasury.
It keeps Medici's rule that new coins only come from the decree (ECONOMY §2):
your mod registers a purse, a Medici bank account (trader:<purse id>) that takes in the coins your traders
earn and pays out from them first, then from the decree's street share. Registering a purse turns that share
on; a server without your mod keeps the old split.
Compile against the API jar from the release (medici-<version>-api.jar, only de.benkl.medici.api, with sources)
and guard every call with ModList.get().isLoaded("medici"):
dependencies { compileOnly files("libs/medici-1.21.1-1.1.0-api.jar") }
// during setup (any thread)
MediciTreasury.registerPurse("mymod:street");
// on the server thread
long paid = MediciTreasury.pay(player, "mymod:street", 250, "mymod:contract"); // 0..250, purse first, then the street
if (MediciTreasury.charge(player, "mymod:street", 40, "mymod:shop")) { /* hand over the goods */ }
Component price = Component.literal(MediciTreasury.format(40)); // "4G"
| method | what |
|---|---|
registerPurse(id), isRegistered(id) |
register your purse during setup (idempotent); turns the street share on |
available(server, purse) |
the purse's balance plus today's street pool left (0 if not registered) |
withdraw(server, purse, pennies, reason) |
up to pennies for a payout, purse first, then new coins from the street pool (minted, in the ledger under reason); you hand out exactly the granted amount |
deposit(server, purse, pennies, reason) |
coins your trader took in (you removed the items) go into the purse |
burn(server, pennies, reason) |
a sink of yours destroyed coins (you removed the items); a ledger burn |
pay(player, purse, pennies, reason) |
withdraw and give the coins (purses first, then the inventory, dropped if full); returns what was paid |
charge(player, purse, pennies, reason) |
take the coins (purses first, change given back) into the purse; false and nothing moves if they can't pay |
coins(pennies), value(stack), wallet(player) |
coin stacks worth an amount (largest first), what a stack is worth, what a player carries |
appraisal(stack), format(pennies) |
the Merchant's price for one item (the appraisal table), 1D 4G 5P |
Amounts are pennies. Everything but registerPurse/isRegistered runs on the server thread only. A purse id that
isn't registered makes withdraw, deposit, pay and charge throw IllegalArgumentException. At each decree
day start a purse keeps at most max(1,000 p, 3 × the street pool); the rest is burned, so pay out what you take in.
Add your own art
Everything the Artist paints is data (concept §4.9.3), read on /reload; a bad file is logged and skipped. Ids are
<namespace>:<path>; names come from lang keys art.<ns>.<path with dots> (motifs), frame.<ns>.<id>,
pose.<ns>.<id>, theme.<ns>.<id>.
- Motifs
data/<ns>/medici/art/motif/**/*.json:{ "kind": "portrait", "theme": "medici:canal", "rarity": "uncommon", "set": "my_pack", "texture": "mypack:art/background/harbour", "masterwork_texture": "mypack:art/background/harbour_night" }kind:portrait(texturestextures/<texture>_<w>x<h>.pngfor 1x1, 1x2, 2x2, 3x2 at 16 px per block),city_view("painting": "mypack:harbour"= apainting_variantyou also add, with"sizes": ["3x2"];"dimension"hides it until that dimension exists),fresco(textures/<texture>_<w>x<h>.pngon the block atlas, i.e. undertextures/block/, sizes 2x2, 3x3, 4x3, 6x4),mask(texture namespace + pattern id:textures/models/armor/mask/<shape>_<pattern>.pnganditem/mask/<shape>_<pattern>.png),crest("pattern": abanner_pattern),statue("ramp": ["#rrggbb", …]dark → light),colossus("scheme": {"shades": […], "core", "ornament", "eyes", "plinth", "plinth_trim"}, block ids).rarity(common 6 : uncommon 3 : rare 1 weight) decides the odds: Sketch draws common, Study common and uncommon, Commission and up everything. Optional"sizes"limits a motif to some sizes. - Frames
data/<ns>/medici/art/frame/*.json:{"family": "wood", "texture": "mypack:art/frame/oak", "shape": "rect"}; the texture is a 16×16 nine-slice with a 3 px border; familiesplain(Sketch),wood(Study),gilt(Grand, or Commission with gold leaf), anything else from Commission tier; shapesrect,oval,round. - Poses
data/<ns>/medici/art/pose/*.json:{"kinds": ["portrait", "statue"], "bust_only": false}; the built-in drawing knowsstanding,hand_on_hip,holding_ducat,ledger_and_quill,gondolier_oar,masked,pointing,seated,orator,saluting(other ids draw as standing). - Themes
data/<ns>/medici/art/theme/*.json:{"order": 20}.
Missing textures show a plain canvas (portraits) or the missing-texture sprite (frescos), never a crash. To change
prices or pools in code, listen to de.benkl.medici.arts.CommissionEvent (see INTEGRATIONS).
