Imported from Nighterezi/ThirstWasTaken2 (
src/main/resources/AGENTS.md). Install upstream withnpx skills add Nighterezi/ThirstWasTaken2 --skill resources. Copyright stays with the author.
src/main/resources
The hand-written half of the mod's assets, plus the mixin config. This file is excluded from the
built jar by processResources in build.gradle.kts; keep that exclude if you add more notes here.
The loader's own manifest is loader code and lives beside the loader's Java, in
src/main/fabric/resources/fabric.mod.json and src/main/neoforge/resources/META-INF/neoforge.mods.toml.
Everything here is shared by every loader.
thirstwastaken2.mixins.json every common mixin class must be listed here
assets/thirstwastaken2/
icon.png the Mod Menu icon, 512x512
textures/ block, item, gui, font and mob effect sheets
models/block/ the copper hanging pot, and its frame and item templates, from Blockbench
font/droplets.json bitmap font for the tooltip droplet rows
lang/ 9 locales
Every other JSON is generated, apart from the hanging pot's Blockbench models below, and lives in
src/main/generated/<minecraft version>/, which is a second resource root of main. Recipes,
advancements, tags, the damage type, item models and model definitions are all written by
src/datagen; editing one by hand is undone by the next runDatagen and caught by checkDatagen.
Read src/datagen/java/AGENTS.md before changing any of them. The
notes below describe what those files mean, which is what you need in order to change the generator
that writes them.
Manifests
There are two manifests, one per loader, and they must keep saying the same thing: the version, name, authors, icon, the Minecraft range, the optional mods and the mixin configs.
src/main/fabric/resources/fabric.mod.jsonis templated:${version}is expanded byprocessResources, so it is not valid JSON to a strict parser until built. Its entrypoints are the thin Fabric classesThirstWasTaken2FabricandThirstWasTaken2FabricClient, not the loader independentThirstWasTaken2.src/main/neoforge/resources/META-INF/neoforge.mods.tomlis templated the same way, bybuild.neoforge.gradle.kts, which expands comments too. It names no entrypoints: FML finds the@ModclassesThirstWasTaken2NeoForgeandThirstWasTaken2NeoForgeClientby annotation.orderingandsidemust be upper case.
A new mixin class needs an entry in its config or it never applies. A new mixin config file needs
an entry in both manifests. FML has no per-side mixin config, so a client config keeps its classes in
its own "client" list, which is what thirstwastaken2.client.mixins.json in src/client/resources
does; Fabric also marks it "environment": "client".
The Create Fly Sand Filter is the exception to both of the above. Its assets, data files and mixin
config are hand-written and live in src/main/createfly/resources, and processResources adds its
entrypoints and mixin config to the built manifest, because only the builds that compile it may name
them. See src/main/createfly/AGENTS.md.
Recipes
All 18 purify recipes are generated by hand from one shape: purify_water_<container>_<purity>_<method>
for container ∈ {bottle, bowl, bucket}, input purity ∈ {0, 1, 2}, method ∈ {smelting, campfire}.
Purity 3 has no recipe because it is already clean.
- The mapping is 0 → 2, 1 → 3, 2 → 3 (a +2 bump capped at 3), identical for both methods.
- Smelting is
cookingtime200, campfire 600, bothexperience0.35. - Matching on a component uses Fabric's ingredient type:
{"fabric:type": "fabric:components", "base": …, "components": {"thirstwastaken2:water_purity": N}}. Datagen only writes Fabric's spelling; the NeoForge node translates it,fabric:anyandfabric:load_conditionsas it copies resources, inbuild.neoforge.gradle.kts, andcheckNeoForgeResourcesfails if afabric:key survives. A new Fabric-only shape in a generator needs a translation there too. Water bottles must also match"minecraft:potion_contents": "minecraft:water"on top of theminecraft:potionbase. - Every cooking ingredient also requires
water_salty: false, and every result writes back the new purity andwater_salty: false. That requirement is what keeps sea water out of the furnace, since salt water carries nowater_purityat all — but it also means anything that hands out water has to stamp both components, loot included, or its bottles can never be boiled. Bowl results additionally write custom model data index 1 so their sprite is right without runtime work. - The purity table lives in
ThirstRecipeProvider.PURIFY_TABLEand the recipes carry it independently ofWaterPurity— the Java side has no idea these exist. Changing it rewrites all 18 files at once, which is the whole reason they are generated.
Advancements
advancement/ holds two unrelated things.
advancement/*.json is the mod's own tab, rooted at root. Every one of them except root and
boil_water has a single minecraft:impossible criterion named thirst and is granted from
com.thirstwastaken2.advancement.ThirstAdvancements by id. That is a deliberate trade: a custom
trigger would be the idiomatic answer, but the trigger classes moved package twice across the three
supported Minecraft versions, and awarding by id uses one API that is the same on all of them. The
cost is that datapacks cannot write their own conditions against these events.
Renaming one of those means renaming the constant in ThirstAdvancements with it, or the
advancement silently stops being awarded. AdvancementGameTest is what catches that.
boil_water is the exception: a furnace credits the player who takes the result, so it uses
vanilla's minecraft:recipe_crafted with one criterion per smelting recipe and a single requirements
list, which makes them an OR. The nine campfire recipes cannot be in it, because a campfire has no
player to credit.
advancement/recipes/misc/*.json are ordinary recipe-book unlocks with no display, one per
craftable plus one per container for the purify recipes, six at a time. Without them the mod's
recipes never appear in the recipe book at all.
Tags
data/minecraft/tags/damage_type/bypasses_armor.json is "required": false on purpose: it refers to
the mod's own damage type.
Item models
26.2 splits item models in two: items/<id>.json is the definition (what to select), models/item/
holds the model it points at.
items/waterskin.json is the only non-trivial one: a range_dispatch over
minecraft:custom_model_data index 0, with thresholds 1, 2 and 3 selecting waterskin_1,
waterskin_2 and waterskin, and waterskin_0 as the fallback. The dispatch value is written by
WaterskinItem.setServings, which mirrors the water_servings component into CUSTOM_MODEL_DATA as
a float (and removes the component at zero) — the two must always be set together, which is why
nothing outside that helper writes WATER_SERVINGS.
items/terracotta_water_bowl.json dispatches custom model data float index 1 to five pixel-art water
colors: 0..3 for the grades and 4 for sea water. WaterPurity.setQuality writes that value for
sampled bowls; cooking recipes write it in the result. The PNG variants are reproducibly derived from
the original sprite by tools/generate_water_sprites.py, salt included. Waterskins deliberately do
not display quality in their sprite, only in their durability-bar colour.
items/salt_water_bottle.json and items/salt_water_bucket.json exist for vanilla's containers,
which cannot be given a model of ours at registration. WaterPurity.setQuality points the
minecraft:item_model component at them when the water is salty and clears it when it is not, so a
sea-water bottle never looks like a drinkable one.
The bottle one is worth copying rather than reinventing: it reuses vanilla's own
minecraft:item/potion model and only replaces the tint, minecraft:potion becoming a
minecraft:constant of the sea colour. It ships no texture, so the bottle keeps whatever shape the
player's resource pack gives potions. The bucket has no tinted overlay layer to borrow, so
textures/item/salt_water_bucket.png is a one-off recolour of vanilla's water bucket: same bucket,
sea-coloured water.
The hanging pots
models/block/copper_hanging_pot.json (the pot) and models/block/template_copper_hanging_pot_frame.json
(the frame, with an open #chain slot) are Blockbench models adapted from Dehydration, GPL-3.0; see
CREDITS.md at the root. So is textures/block/copper_hanging_pot.png. They stay hand-written because
datagen cannot build elements.
textures/block/iron_hanging_pot.png is that texture recoloured to cast iron, with the same layout. The iron pot has no models of its own here:
HangingPotModels writes children of the copper ones that only swap the texture, so any edit to the
copper models reaches both pots. A texture edit has to keep the two layouts matching.
Neither texture is used for particles. A break particle is a random piece of the particle texture,
and these sheets are mostly wood, empty space and the texture artist's mark in a corner no face
uses. The copper models name minecraft:block/copper_block as particle, and the iron ones
minecraft:block/cauldron_side. Keep it that way when re-exporting from Blockbench, which writes
the sheet back into particle.
The item has no sprite. models/block/template_copper_hanging_pot_item.json is the pot and the frame
in one model, with display transforms that shrink the 31 pixel tall pot into a slot, so the item is
drawn in 3D like a chest. It repeats the elements of the other two, so an edit to either model has to
be copied into it.
Everything else the pots show is generated by HangingPotModels: a multipart blockstate per pot, the
frame model that fills #chain with the chain texture of its version, and a flat water surface per
fill level and quality, which both pots share. The frame keeps "render_type": "minecraft:cutout" for NeoForge before 26.1; the
other loaders ignore the key.
The water surfaces use textures/block/copper_hanging_pot_water_<grade>.png, animated strips drawn by
tools/generate_pot_water.py in the filled bowl's palette. The pot is not tinted at runtime, which is
what spares it a colour handler per loader and version. Rerun the script after changing a palette.
The mod icon
icon.png is the one asset the mod list shows, and it draws it at 32 pixels, which the wordmark
logo could not survive. It is the full droplet frame of textures/gui/thirst_icons.png scaled up 32
times on a padded 400x400 canvas, by tools/generate_mod_icon.py, which writes the identical file to
docs/public/logo-small.png in the same run. Recolouring the thirst bar therefore means rerunning
that script, or the mod list keeps the old droplet. The wordmark stays the docs hero and Modrinth
artwork, where there is room to read it.
Fonts and GUI sheets
font/droplets.json maps U+E000..U+E011 onto textures/font/droplets.png at height 9, ascent 8.
ThirstTooltip uses U+E000/U+E001 for the filled thirst row. The quenched row uses U+E004/U+E007
(plain blue outline) when the overlay is OFF, and a full/half pair from U+E008 for each coloured
QuenchedOverlay, in enum order. U+E002, U+E003, U+E005 and U+E006 remain on the sheet for
compatibility but are not emitted. tools/generate_quenched_overlay.py writes the glyphs from U+E008.
textures/gui/thirst_icons.png is 41x9 (five frames, 8px stride), and thirst_icons_parched.png
beside it is the same sheet in sand for the Parched effect, drawn by tools/generate_parched_icons.py.
textures/gui/quenched_overlay.png is 36x45 (four frames per row, one row per coloured
QuenchedOverlay), and textures/gui/appleskin_icons.png is a 256x256 sheet whose only content is
the v = 18 AppleSkin exhaustion strip. ThirstHud documents the exact blits.
Mob effect icons
textures/mob_effect/<effect id>.png, 18x18, drawn by hand. The game finds an effect's icon by its
registry id, on every version, so an icon needs no JSON and no code. parched.png is the Parched
effect's dry tongue.
Lang
Nine locales: en_us, fr_fr, ja_jp, ko_kr, pl_pl, ru_ru, vi_vn, zh_cn, zh_tw.
en_us and vi_vn are the maintained pair — a new key must land in both; the rest are best-effort
and may lag.
Key families, and who reads them:
| Prefix | Written by |
|---|---|
thirstwastaken2.config.* (+ .tooltip, .category.*) |
the config screens, derived from the snake_case widget key |
thirst.purity.* |
WaterPurity.tooltip and the chance sliders |
thirst.water.salty |
WaterPurity.saltTooltip, the one line salt water gets instead of a grade |
tooltip.thirstwastaken2.* |
ThirstTooltip: waterskin contents and the clay bowl hint |
item.thirstwastaken2.*, block.thirstwastaken2.*, effect.thirstwastaken2.*, itemGroup.thirstwastaken2 |
registration |
command.thirstwastaken2.* |
ThirstCommands |
advancements.thirstwastaken2.* |
the title and description of every advancement in data/thirstwastaken2/advancement/ |
death.attack.dehydrate* |
the dehydrate damage type's message_id |
config.jade.plugin_thirstwastaken2.* |
Jade's plugin settings screen, one per provider getUid() |
Player-facing wording in en_us.json is what the docs site quotes, so keep the two in step (see
docs/AGENTS.md).