The Place2Die server resource pack: one pack, required on join, carrying the models and textures of every custom item (and, through BetterModel, every custom mob).
Targets Minecraft Java 26.2 (Paper 26.2): resource pack format 88
(min_format/max_format in pack/pack.mcmeta, the format scheme since 1.21.9).
pack/ the pack sources, zipped as-is
pack.mcmeta
assets/
p2d_survival/ one namespace per plugin = the plugin's key namespace
items/<id>.json item definition, read by the item_model component
models/item/<id>.json the model
textures/item/<id>.png the texture
p2d_gathering/ items (materials, tools) and harvest node models
p2d_building/ the tool cupboard (item and block display)
textures/block/<id>.png node textures (cube models shown by an item display)
p2d_loot/ loot barrel and crate models (item displays over the containers)
p2d_zombies/ … (added with their first asset)
bettermodel/ or bettermodel.zip optional, BetterModel's generated pack (see below)
tools/p2dpack/ build tooling (Python 3.13, standard library only)
tests/ unittest suite of the tooling
scripts/placeholders.py generates the placeholder textures
pack.py entry point
./pack.py validate # check the sources
./pack.py build # validate, merge BetterModel's pack, zip, print the SHA-1
./pack.py version # next release tag computed from the gitmojis since the last tag
python3 -m unittest discover -s tests -t .build writes build/p2d-resourcepack.zip and build/p2d-resourcepack.zip.sha1. The zip
is deterministic (sorted entries, fixed timestamps and permissions), so the same sources
always give the same SHA-1.
pack.mcmetaparses and itsmin_format..max_formatrange includes 88.- Every
.json/.mcmetaparses, with no duplicate keys. - Namespaces use
[a-z0-9_.-]and asset paths[a-z0-9_./-](lowercase): Minecraft silently ignores anything else. Onlypack.mcmeta,pack.pngandassets/at the root. - Every
.pngis a real PNG. - Every item definition (
assets/<ns>/items/*.json) points to models that exist, at any depth (selectcases,condition,range_dispatch,composite,specialbases). - Every model's parent and textures exist. A texture outside
item/orblock/is a warning: it is only stitched into the atlas if the pack adds an atlas source for it.
References into the minecraft namespace (item/generated, vanilla textures) are vanilla
assets, so they are trusted, not checked.
Every custom item has its own model and texture (rule decided on 2026-10-15). For an item
p2d_<plugin>:<id>:
- Texture:
pack/assets/p2d_<plugin>/textures/item/<id>.png, 16x16 (or a larger power of two). Lowercase file names. - Model:
pack/assets/p2d_<plugin>/models/item/<id>.json({ "parent": "minecraft:item/generated", "textures": { "layer0": "p2d_<plugin>:item/<id>" } }minecraft:item/handheldfor tools and weapons; a Blockbench export for 3D items.) - Item definition:
pack/assets/p2d_<plugin>/items/<id>.json{ "model": { "type": "minecraft:model", "model": "p2d_<plugin>:item/<id>" } } ./pack.py buildandpython3 -m unittest discover -s tests -t .must pass.- Commit with
:bento:(assets, patch release) or:sparkles:, ending with the ticket id, and open a PR. Once it is merged and released, the plugin can set the model:CustomItem.Builder#model(ItemModel.model(Key.key("p2d_<plugin>", "<id>"))). Never setitem_modelbefore the released pack ships the asset: the item renders as the missing texture.
Core custom blocks can be rendered by an item display whose item_model is the block's key
(harvest nodes do: an invisible barrier hitbox under the model; p2d-loot's barrels too, and its
crates show theirs over the chest that holds the loot). They follow the same three
steps; a block-shaped placeholder uses "parent": "minecraft:block/cube_all" with
"textures": { "all": "p2d_<plugin>:block/<id>" } and its texture under textures/block/.
Tree nodes come in sizes (PERSO-113): tree_small, tree (medium) and tree_large, each with a
_depleted model. Their placeholders are boxes of different sizes (elements with
parent: minecraft:block/block) so the sizes read apart in game before the real models exist.
Each tree size also has a _falling model (PERSO-114): the part of the tree above its stump, shown
by a display p2d-gathering spawns over the stump when the tree is cut, then tilts to the ground
around the middle of the block's bottom face. Keep its foot at the stump's top and the trunk centred
on x/z = 8, or it will not fall from the stump.
These textures are placeholders generated by scripts/placeholders.py, waiting for
real art (PERSO-83). Replace the PNG and remove its entry from the script.
| Item | Texture |
|---|---|
p2d_survival:canteen |
assets/p2d_survival/textures/item/canteen.png |
p2d_survival:bandage |
assets/p2d_survival/textures/item/bandage.png |
p2d_survival:medicine |
assets/p2d_survival/textures/item/medicine.png |
p2d_gathering:wood |
assets/p2d_gathering/textures/item/wood.png |
p2d_gathering:stone |
assets/p2d_gathering/textures/item/stone.png |
p2d_gathering:raw_metal |
assets/p2d_gathering/textures/item/raw_metal.png |
p2d_gathering:hatchet |
assets/p2d_gathering/textures/item/hatchet.png |
p2d_gathering:pickaxe |
assets/p2d_gathering/textures/item/pickaxe.png |
p2d_gathering:tree (node display) |
assets/p2d_gathering/textures/block/tree.png |
p2d_gathering:tree_depleted (node display) |
assets/p2d_gathering/textures/block/tree_depleted.png |
p2d_gathering:tree_small (node display) |
assets/p2d_gathering/textures/block/tree_small.png |
p2d_gathering:tree_small_depleted (node display) |
assets/p2d_gathering/textures/block/tree_small_depleted.png |
p2d_gathering:tree_large (node display) |
assets/p2d_gathering/textures/block/tree_large.png |
p2d_gathering:tree_large_depleted (node display) |
assets/p2d_gathering/textures/block/tree_large_depleted.png |
p2d_gathering:tree_falling (falling tree display) |
assets/p2d_gathering/textures/block/tree_falling.png |
p2d_gathering:tree_small_falling (falling tree display) |
assets/p2d_gathering/textures/block/tree_small_falling.png |
p2d_gathering:tree_large_falling (falling tree display) |
assets/p2d_gathering/textures/block/tree_large_falling.png |
p2d_gathering:rock (node display) |
assets/p2d_gathering/textures/block/rock.png |
p2d_gathering:rock_depleted (node display) |
assets/p2d_gathering/textures/block/rock_depleted.png |
p2d_gathering:ore (node display) |
assets/p2d_gathering/textures/block/ore.png |
p2d_gathering:ore_depleted (node display) |
assets/p2d_gathering/textures/block/ore_depleted.png |
p2d_loot:barrel (loot barrel display) |
assets/p2d_loot/textures/block/barrel.png |
p2d_loot:crate (loot crate display) |
assets/p2d_loot/textures/block/crate.png |
p2d_building:tool_cupboard (item and block display) |
assets/p2d_building/textures/block/tool_cupboard.png |
BetterModel (PERSO-84) renders the Blockbench mob models and generates its own pack. Players
must get one pack, so build merges it into ours:
- Input:
./bettermodel/(BetterModel'spack-type: folderoutput) or./bettermodel.zip(pack-type: zip), or any path with./pack.py build --bettermodel <path>. Absent = nothing merged. - Only its
assets/are merged. Itspack.mcmetaandpack.pngare replaced by ours. - A file both packs ship with different content is a conflict and fails the build; a byte-identical file is kept once.
- Anything else at its root (overlay folders) fails the build: set BetterModel's
pack.generate-legacy-model: false, we only target 26.2. - The merged pack is validated again before zipping.
CI (.github/workflows/build.yml) runs the tests and the build on every PR and push, checks
the zip is reproducible, and uploads it as an artifact. On main, the version is computed
from the gitmojis since the last vX.Y.Z tag (:boom: major, :sparkles: minor, patch
gitmojis such as :bento: and :bug: patch, others no release) and a GitHub release
vX.Y.Z is published with the zip and its SHA-1. Nobody tags by hand.
The repository is private, so release assets are not downloadable by Minecraft clients: the pack must be served from a public URL (see the open question in PERSO-81).