minecraft-resource-pack
Create and debug Minecraft 26.x and 1.21.x resource packs, including pack metadata, textures, models, blockstates, item definitions, sounds, fonts, animations, and shaders. Use for client-side visual or audio assets without gameplay code.
Install
npx skills add https://github.com/Jahrome907/minecraft-agent-skills/tree/main/plugins/minecraft-codex-skills/skills/minecraft-resource-pack
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install jahrome907-minecraft-agent-skills@llmmart
git clone https://github.com/Jahrome907/minecraft-agent-skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole jahrome907/minecraft-agent-skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Minecraft Resource Pack Skill
What Is a Resource Pack?
A resource pack is a folder (or .zip) that overrides or adds Minecraft's visual and
audio assets: textures, models, sounds, language files, and fonts. No Java or mod
loader is required for those vanilla assets.
Routing Boundaries
Use when: the deliverable is visual/audio assets (textures, models, sounds, fonts) in resource-pack format.Do not use when: the task requires gameplay logic or runtime behavior changes (useminecraft-datapack,minecraft-plugin-dev, orminecraft-modding).Do not use when: the task is server infrastructure/runtime administration (minecraft-server-admin).
Pack Metadata
| Minecraft Version | Preferred pack metadata |
|---|---|
| 1.21 / 1.21.1 | pack_format: 34 |
| 1.21.2 / 1.21.3 | pack_format: 42 |
| 1.21.4 | pack_format: 46 |
| 1.21.5 | pack_format: 55 |
| 1.21.6 | pack_format: 63 |
| 1.21.7 / 1.21.8 | pack_format: 64 |
| 1.21.9 / 1.21.10 | min_format: [69, 0], max_format: [69, 0] |
| 1.21.11 | min_format: [75, 0], max_format: [75, 0] |
| 26.1 | min_format: [84, 0], max_format: [84, 0] |
| 26.2 | min_format: [88, 0], max_format: [88, 0] |
Use legacy pack_format only for resource formats below 65 (through 1.21.8).
Resource format 65 and later require both min_format and max_format; use
[major, minor] for exact patch targeting, including .0 such as [84, 0].
An integer or [major] min_format means [major, 0]; an integer or
[major] max_format permits every minor version on that major line. Do not
write decimal JSON numbers. A legacy-only pack can use its integer
pack_format alone. A range whose min_format includes a legacy format needs
integer pack_format and supported_formats; do not include
supported_formats for a modern-only range.
Directory Layout
my-pack/
├── pack.mcmeta
├── pack.png ← 64×64 icon (optional)
└── assets/
└── minecraft/ ← override vanilla (or <namespace>/ for new packs)
├── models/
│ ├── block/
│ │ └── stone.json
│ └── item/
│ └── diamond_sword.json
├── items/ ← 1.21.4+ item model definitions
│ └── diamond_sword.json
├── blockstates/
│ └── stone.json
├── textures/
│ ├── block/
│ │ └── stone.png
│ ├── item/
│ │ └── diamond_sword.png
│ ├── gui/
│ │ └── sprites/
│ │ └── my_sprite.png
│ └── entity/
│ └── zombie/
│ └── zombie.png
├── sounds/
│ └── custom/
│ └── my_sound.ogg
├── sounds.json
├── font/
│ └── default.json
└── lang/
└── en_us.json
Client-mod-specific files such as OptiFine CIT and Iris shader packs use their own formats. Read conditional assets before adding them.
pack.mcmeta
1.21.8 and earlier
{
"pack": {
"pack_format": 64,
"description": "My Custom Resource Pack v1.0"
}
}
1.21.9 / 1.21.10
{
"pack": {
"min_format": [69, 0],
"max_format": [69, 0],
"description": "My Custom Resource Pack v1.0"
}
}
1.21.11
{
"pack": {
"min_format": [75, 0],
"max_format": [75, 0],
"description": "My Custom Resource Pack v1.0"
}
}
26.2
{
"pack": {
"min_format": [88, 0],
"max_format": [88, 0],
"description": "My Custom Resource Pack v1.0"
}
}
Block Models
assets/minecraft/models/block/my_cube.json
Full cube — all six faces use the same texture:
{
"parent": "minecraft:block/cube_all",
"textures": {
"all": "minecraft:block/stone"
}
}
Column block (like logs):
{
"parent": "minecraft:block/cube_column",
"textures": {
"end": "mypack:block/my_pillar_top",
"side": "mypack:block/my_pillar_side"
}
}
Different sides:
{
"parent": "minecraft:block/cube",
"textures": {
"up": "mypack:block/my_block_top",
"down": "mypack:block/my_block_bottom",
"north": "mypack:block/my_block_side",
"south": "mypack:block/my_block_side",
"east": "mypack:block/my_block_side",
"west": "mypack:block/my_block_side",
"particle": "mypack:block/my_block_side"
}
}
Cross model (flowers, plants):
{
"parent": "minecraft:block/cross",
"textures": {
"cross": "mypack:block/my_flower"
}
}
Custom geometry (elements)
{
"credit": "Custom model",
"ambientocclusion": true,
"textures": {
"0": "mypack:block/panel",
"particle": "mypack:block/panel"
},
"elements": [
{
"from": [0, 0, 7],
"to": [16, 16, 9],
"faces": {
"north": { "texture": "#0", "uv": [0, 0, 16, 16] },
"south": { "texture": "#0", "uv": [0, 0, 16, 16] }
}
}
],
"display": {
"thirdperson_righthand": {
"rotation": [75, 45, 0],
"translation": [0, 2.5, 0],
"scale": [0.375, 0.375, 0.375]
}
}
}
fromandtoare in 1/16th block units (0–16).uvis[x1, y1, x2, y2]in 0–16 units.
Item Models
For 1.21.4 and later, use item definitions in
assets/<namespace>/items/. For 1.21.3 and earlier, use the legacy model
overrides array. Read conditional assets
for the string-based current custom_model_data selector and the legacy numeric
predicate; their values are not interchangeable.
Blockstate Definitions
Simple block (no variants)
{
"variants": {
"": { "model": "mypack:block/my_block" }
}
}
Facing block (4 rotations)
{
"variants": {
"facing=north": { "model": "mypack:block/my_block" },
"facing=south": { "model": "mypack:block/my_block", "y": 180 },
"facing=east": { "model": "mypack:block/my_block", "y": 90 },
"facing=west": { "model": "mypack:block/my_block", "y": 270 }
}
}
Random texture (multipart)
{
"variants": {
"": [
{ "model": "minecraft:block/grass_block", "weight": 3 },
{ "model": "minecraft:block/grass_block_2" }
]
}
}
Multipart (slabs, fences, walls)
{
"multipart": [
{ "apply": { "model": "mypack:block/my_slab_bottom" }, "when": { "type": "bottom" } },
{ "apply": { "model": "mypack:block/my_slab_top" }, "when": { "type": "top" } },
{ "apply": { "model": "mypack:block/my_block" }, "when": { "type": "double" } }
]
}
Textures
- Format: PNG, RGBA (32-bit)
- Standard block/item size: 16×16 px
- Textures can be larger (32×32, 64×64) — Minecraft scales them, but stick to powers of 2
- Animation requires height = N × width (e.g., 16×64 for 4 frames)
- Place block textures in
assets/<namespace>/textures/block/ - Place item textures in
assets/<namespace>/textures/item/ - All textures are referenced without the
.pngextension in JSON
Animated texture MCMETA
assets/minecraft/textures/block/fire_0.png.mcmeta:
{
"animation": {
"frametime": 2,
"frames": [0, 1, 2, 3, 4, 5, 6, 7]
}
}
If frames is omitted, all frames play sequentially. frametime is in game ticks (default 1).
GUI sprites (1.20.2+)
Place sprites at assets/minecraft/textures/gui/sprites/<category>/<name>.png.
Reference them with <category>/<name> in code/JSON.
26.1 block-model texture entries
For 26.1, a block-model textures entry may remain a sprite string or use an
object. The object must have a string sprite; force_translucent, when set,
must be a boolean.
{
"textures": {
"all": {
"sprite": "mypack:block/frosted_panel",
"force_translucent": true
}
}
}
Both forms identify a sprite without .png. Use force_translucent only when
the geometry must render in the translucent pass despite its sprite pixels.
Sounds
An event's namespace comes from the namespace containing sounds.json; a
sound entry's name identifies the sound-file namespace. Sound files are Vorbis
.ogg under assets/<namespace>/sounds/. Read
conditional assets for a correct event
example, aliases, sound-source selection, and replacement behavior.
Language Files
assets/minecraft/lang/en_us.json:
{
"block.mypack.my_block": "My Custom Block",
"item.mypack.my_item": "Magic Wand",
"entity.mypack.my_mob": "Forest Guardian",
"death.attack.mypack.laser": "%1$s was zapped by %2$s"
}
- Use the exact translation key format for your mod/datapack namespace
- File name is the locale code (e.g.,
fr_fr.json,de_de.json) - Always provide
en_us.jsonas the primary fallback
Fonts
Read conditional assets for bitmap provider layout and private-use icon guidance.
OptiFine and shaders
OptiFine CIT and Iris shader packs have client-mod-specific formats. Read conditional assets before adding either; they are not portable vanilla resource-pack features.
Installation
# Singleplayer: place in
~/.minecraft/resourcepacks/my-pack/
# or
~/.minecraft/resourcepacks/my-pack.zip
# Server-side (forces on clients):
# Set in server.properties:
resource-pack=https://example.com/my-pack.zip
resource-pack-sha1=<sha1 hash>
resource-pack-prompt={"text":"Required pack","color":"gold"}
Common Issues
| Problem | Cause | Fix |
|---|---|---|
| Model not showing | Wrong JSON path or syntax error | Check assets/<namespace>/models/ path; validate JSON |
| Black/pink checkerboard | Texture path wrong or missing | Check textures/ path, file extension not in JSON |
| Blockstate not applying | Wrong state property name | Use F3 to inspect block state; use /data get block <x> <y> <z> for block-entity NBT |
| Animation not working | Wrong MCMETA location | Must be same folder as texture, named texture.png.mcmeta |
| Custom sound not playing | Not in sounds.json |
Register sound event in sounds.json, match namespace |
| Pack not loading | Wrong pack_format or min_format / max_format values |
Update pack.mcmeta for the exact 1.21.x patch |
Validator Script
Use the bundled validator script before shipping a resource-pack update:
Unbundled references to another namespace's models, textures, sounds, or fonts are warnings requiring runtime verification with the dependency present. Missing files in the current namespace fail; strict mode also fails unresolved warnings.
# Run from the installed skill directory (for example `.claude/skills/minecraft-resource-pack`):
./scripts/validate-resource-pack.sh --root /path/to/resource-pack
# Strict mode treats warnings as failures:
./scripts/validate-resource-pack.sh --root /path/to/resource-pack --strict
What it checks:
- JSON validity for
pack.mcmetaandassets/**/*.json - Legacy versus modern
pack.mcmetafield shape, including integer,[major], and[major, minor]versions - Model/blockstate/font/sounds references resolve to real files
- Current
custom_model_dataselect cases use strings; 26.1 texture objects use a stringspriteand optional booleanforce_translucent - Same-namespace
type: "event"sound aliases name an event in thatsounds.json - Every
*.png.mcmetahas a matching*.png
The validator cannot resolve a sound event owned by another namespace, or prove that a command or mod code invokes an event with the intended sound source. Those cases warn and require an exact-client runtime check.
References
- Mojang: Java Edition 1.21.4
- Mojang: Java Edition 1.21.9
- Mojang: Java Edition 26.1
- NeoForge 1.21.8: Sounds
- Misode's model viewer: https://misode.github.io/
- OptiFine CIT guide: https://optifine.readthedocs.io/cit.html
Files (minecraft-agent-skills)
-
references
-
conditional-assets.md 3.7 KB
# Conditional Resource-Pack Assets Read this reference only when the requested pack uses current or legacy item models, sounds, fonts, OptiFine, or shaders. Keep each feature within the target client's supported format. ## Current and legacy item models For **1.21.4 and later**, place item definitions in `assets/<namespace>/items/<item>.json`. The `minecraft:custom_model_data` string property reads the component's `strings` list. A `when` value can be one string or a non-empty list of strings; numeric values such as `1001` are not valid for this selector. ```json { "model": { "type": "minecraft:select", "property": "minecraft:custom_model_data", "fallback": { "type": "minecraft:model", "model": "minecraft:item/stick" }, "cases": [ { "when": ["magic_wand"], "model": { "type": "minecraft:model", "model": "mypack:item/magic_wand" } } ] } } ``` Give the matching item component with a string value: ```mcfunction /give @s minecraft:stick[custom_model_data={strings:["magic_wand"]}] ``` For **1.21.3 and earlier**, use a model's legacy `overrides` array and numeric `predicate.custom_model_data` values. Do not mix the legacy override format into the current `items/` definition. ```json { "parent": "minecraft:item/handheld", "textures": { "layer0": "minecraft:item/stick" }, "overrides": [ { "predicate": { "custom_model_data": 1001 }, "model": "mypack:item/magic_wand" } ] } ``` ## Sounds The namespace of an event is the namespace containing its `sounds.json`. The namespace in a sound entry's `name` identifies the sound-file namespace. Put a custom event in `assets/mypack/sounds.json` and its file at `assets/mypack/sounds/ui/click.ogg`. ```json { "ui.click": { "sounds": [ { "name": "mypack:ui/click", "volume": 1.0 } ] }, "ui.click_alias": { "sounds": [ { "name": "mypack:ui.click", "type": "event" } ] } } ``` An omitted `type` is a sound-file entry and resolves to an `.ogg` file. An `event` entry references another sound event and must never be treated as a file path. `sounds.json` maps events to sound objects; it does not choose the sound source. A command chooses its source, for example `/playsound mypack:ui.click ui @s` (the `ui` source is available from 1.21.6), and mod code supplies the relevant `SoundSource`. Use `replace: true` only when intentionally replacing an event inherited from a lower-priority pack. ## Fonts Place font definitions at `assets/<namespace>/font/<name>.json`. A bitmap provider's `file` is a texture resource location including `.png`. ```json { "providers": [ { "type": "bitmap", "file": "mypack:font/icons.png", "ascent": 8, "height": 9, "chars": ["\uE000", "\uE001"] } ] } ``` Use private-use characters deliberately and keep each bitmap cell consistent with its declared height. ## Client-mod and shader boundaries OptiFine CIT files under `assets/minecraft/optifine/` are OptiFine-specific and do not work in vanilla or Iris. Do not present them as a portable item system. Iris shader packs are separate ZIPs with a top-level `shaders/` directory. They are selected through Iris, not loaded as `assets/iris/shaders` inside a resource pack. Vanilla resource packs do not provide a supported way to override core render shaders. Keep shader packs separate from a vanilla resource-pack deliverable, and test them with the exact Iris version named by the user. ## Sources - [Mojang: Java Edition 1.21.4 item models](https://www.minecraft.net/en-us/article/minecraft-java-edition-1-21-4) - [NeoForge 1.21.8 sounds](https://docs.neoforged.net/docs/1.21.8/resources/client/sounds/) - [Iris documentation](https://irisshaders.dev/)
-
-
scripts
-
jq-shim.mjs 12.7 KB · in bundle
-
validate-resource-pack.sh 13.9 KB
#!/usr/bin/env bash set -euo pipefail PASS='[PASS]' WARN='[WARN]' FAIL='[FAIL]' ROOT='.' STRICT=0 while [[ $# -gt 0 ]]; do case "$1" in --root) ROOT="${2:-}" shift 2 ;; --strict) STRICT=1 shift ;; --help|-h) cat <<'USAGE' Usage: validate-resource-pack.sh [--root <path>] [--strict] Checks resource pack integrity: - JSON validity for assets/** and pack.mcmeta - model/blockstate/item-definition/font/sounds references point to existing files - every .png.mcmeta has a matching .png USAGE exit 0 ;; *) echo "$FAIL unknown arg: $1" >&2 exit 1 ;; esac done if ! command -v jq >/dev/null 2>&1; then SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" JQ_SHIM="$SCRIPT_DIR/jq-shim.mjs" if command -v node >/dev/null 2>&1 && [[ -f "$JQ_SHIM" ]]; then jq() { node "$JQ_SHIM" "$@" } else echo "$FAIL jq is required" exit 1 fi fi if [[ ! -d "$ROOT" ]]; then echo "$FAIL root path does not exist: $ROOT" exit 1 fi FAILURES=0 WARNINGS=0 pass() { echo "$PASS $*"; } warn() { echo "$WARN $*"; WARNINGS=$((WARNINGS + 1)); } fail() { echo "$FAIL $*"; FAILURES=$((FAILURES + 1)); } strip_cr() { printf '%s' "${1%$'\r'}"; } check_json() { local file="$1" if jq empty "$file" >/dev/null 2>&1; then pass "valid JSON: ${file#$ROOT/}" else fail "invalid JSON: ${file#$ROOT/}" fi } check_pack_metadata() { local file="$1" local min_parts max_parts min_major min_minor max_major max_minor pack_format local has_pack_format=0 local has_min_format=0 local has_max_format=0 local has_supported_formats=0 if jq -e '.pack | has("pack_format")' "$file" >/dev/null 2>&1; then has_pack_format=1 fi if jq -e '.pack | has("min_format")' "$file" >/dev/null 2>&1; then has_min_format=1 fi if jq -e '.pack | has("max_format")' "$file" >/dev/null 2>&1; then has_max_format=1 fi if jq -e '.pack | has("supported_formats")' "$file" >/dev/null 2>&1; then has_supported_formats=1 fi if jq -e '.pack.pack_format | type == "number" and . == floor' "$file" >/dev/null 2>&1; then pass "pack.mcmeta uses integer pack.pack_format" elif [[ "$has_pack_format" -eq 1 ]]; then fail "pack.mcmeta pack.pack_format must be an integer when present" fi if jq -e '.pack.min_format | ((type == "number" and . == floor) or (type == "array" and (length == 1 or length == 2) and all(.[]; type == "number" and . == floor)))' "$file" >/dev/null 2>&1; then pass "pack.mcmeta uses valid pack.min_format" elif [[ "$has_min_format" -eq 1 ]]; then fail "pack.mcmeta pack.min_format must be an integer or a one/two-integer array" fi if jq -e '.pack.max_format | ((type == "number" and . == floor) or (type == "array" and (length == 1 or length == 2) and all(.[]; type == "number" and . == floor)))' "$file" >/dev/null 2>&1; then pass "pack.mcmeta uses valid pack.max_format" elif [[ "$has_max_format" -eq 1 ]]; then fail "pack.mcmeta pack.max_format must be an integer or a one/two-integer array" fi if [[ "$has_supported_formats" -eq 1 ]] && ! jq -e '.pack.supported_formats | ((type == "number" and . == floor) or (type == "array" and length == 2 and all(.[]; type == "number" and . == floor)) or (type == "object" and (.min_inclusive | type == "number" and . == floor) and (.max_inclusive | type == "number" and . == floor)))' "$file" >/dev/null 2>&1; then fail "pack.mcmeta pack.supported_formats must be an integer, integer range, or min_inclusive/max_inclusive object" fi if [[ "$has_min_format" -ne "$has_max_format" ]]; then fail "pack.mcmeta must define both .pack.min_format and .pack.max_format together" return fi if [[ "$has_min_format" -eq 1 ]]; then if ! jq -e '.pack.min_format | ((type == "number" and . == floor) or (type == "array" and (length == 1 or length == 2) and all(.[]; type == "number" and . == floor)))' "$file" >/dev/null 2>&1 || ! jq -e '.pack.max_format | ((type == "number" and . == floor) or (type == "array" and (length == 1 or length == 2) and all(.[]; type == "number" and . == floor)))' "$file" >/dev/null 2>&1; then return fi min_parts="$(jq -r '.pack.min_format | if type == "number" then "\(.)\t0" elif type == "array" and length == 1 then "\(.[0])\t0" else "\(.[0])\t\(.[1])" end' "$file")" max_parts="$(jq -r '.pack.max_format | if type == "number" then "\(.)\t2147483647" elif type == "array" and length == 1 then "\(.[0])\t2147483647" else "\(.[0])\t\(.[1])" end' "$file")" IFS=$'\t' read -r min_major min_minor <<<"$min_parts" IFS=$'\t' read -r max_major max_minor <<<"$max_parts" if (( min_major > max_major || (min_major == max_major && min_minor > max_minor) )); then fail "pack.mcmeta pack.min_format must not be greater than pack.max_format" return fi if (( min_major < 65 )) && ! jq -e '.pack.pack_format | type == "number" and . == floor' "$file" >/dev/null 2>&1; then fail "pack.mcmeta ranges that include legacy resource pack formats below 65 require integer pack.pack_format" return fi if (( min_major < 65 )) && [[ "$has_supported_formats" -ne 1 ]]; then fail "pack.mcmeta ranges that include legacy resource pack formats below 65 require pack.supported_formats" return fi if (( min_major >= 65 )) && [[ "$has_supported_formats" -eq 1 ]]; then fail "pack.mcmeta must not define pack.supported_formats for modern-only resource pack formats" return fi return fi if jq -e '.pack.pack_format | type == "number" and . == floor' "$file" >/dev/null 2>&1; then pack_format="$(jq -r '.pack.pack_format | numbers' "$file")" if (( pack_format < 65 )); then return fi fail "modern resource pack formats 65 and newer require both .pack.min_format and .pack.max_format" return fi fail "pack.mcmeta must define legacy integer .pack.pack_format or both .pack.min_format and .pack.max_format" } resolve_texture() { local current_ns="$1" local ref="$2" local ns path target [[ -z "$ref" || "$ref" == \#* ]] && return if [[ "$ref" == *:* ]]; then ns="${ref%%:*}" path="${ref#*:}" else ns="$current_ns" path="$ref" fi target="$ROOT/assets/$ns/textures/$path.png" if [[ -f "$target" ]]; then pass "texture exists: $ns:$path" elif [[ "$ns" != "$current_ns" ]]; then warn "external texture not locally verifiable: $ns:$path" else fail "missing texture: $ns:$path (expected ${target#$ROOT/})" fi } resolve_model() { local current_ns="$1" local ref="$2" local ns path target [[ -z "$ref" || "$ref" == builtin/* ]] && return if [[ "$ref" == *:* ]]; then ns="${ref%%:*}" path="${ref#*:}" else ns="$current_ns" path="$ref" fi if [[ "$ns" == "minecraft" ]]; then return fi target="$ROOT/assets/$ns/models/$path.json" if [[ -f "$target" ]]; then pass "model exists: $ns:$path" elif [[ "$ns" != "$current_ns" ]]; then warn "external model not locally verifiable: $ns:$path" else fail "missing model: $ns:$path (expected ${target#$ROOT/})" fi } resolve_sound() { local current_ns="$1" local sound_type="$2" local ref="$3" local sounds_file="$4" local ns path target event if [[ -z "$ref" ]]; then fail "sounds.json entry is missing a name" return fi if [[ "$sound_type" == "event" ]]; then if [[ "$ref" == *:* ]]; then ns="${ref%%:*}" event="${ref#*:}" else ns="$current_ns" event="$ref" fi if [[ "$ns" != "$current_ns" ]]; then warn "external sound event not locally verifiable: $ns:$event" elif jq -r 'keys[]' "$sounds_file" 2>/dev/null | grep -Fqx "$event"; then pass "sound event alias exists: $ns:$event" else fail "missing sound event alias target: $ns:$event" fi return fi if [[ "$sound_type" != "file" ]]; then fail "unsupported sounds.json entry type '$sound_type' for $ref (expected file or event)" return fi if [[ "$ref" == *:* ]]; then ns="${ref%%:*}" path="${ref#*:}" else ns="$current_ns" path="$ref" fi target="$ROOT/assets/$ns/sounds/$path.ogg" if [[ -f "$target" ]]; then pass "sound exists: $ns:$path" elif [[ "$ns" != "$current_ns" ]]; then warn "external sound not locally verifiable: $ns:$path" else fail "missing sound: $ns:$path (expected ${target#$ROOT/})" fi } echo "=== Resource Pack Validator ===" if [[ -f "$ROOT/pack.mcmeta" ]]; then check_json "$ROOT/pack.mcmeta" check_pack_metadata "$ROOT/pack.mcmeta" else fail "missing pack.mcmeta" fi if [[ ! -d "$ROOT/assets" ]]; then fail "missing assets/ directory" fi echo "Checking JSON files under assets/..." while IFS= read -r -d '' json_file; do check_json "$json_file" done < <(find "$ROOT/assets" -type f -name '*.json' -print0 2>/dev/null) echo "Checking model and blockstate references..." while IFS= read -r -d '' model_file; do rel="${model_file#"$ROOT/assets/"}" ns="${rel%%/*}" while IFS= read -r tex; do IFS=$'\t' read -r texture_status texture_key texture_ref <<< "$(strip_cr "$tex")" if [[ "$texture_status" == 'valid' ]]; then resolve_texture "$ns" "$texture_ref" else fail "invalid texture entry for $texture_key in ${model_file#$ROOT/} (use a string or {sprite: string, force_translucent?: boolean})" fi done < <(jq -r '(.textures // {} | to_entries[]? | [(if (.value | type) == "string" then "valid" elif (.value | type) == "object" then if ((.value.sprite? | type) == "string" and ((.value.force_translucent? | type) == "null" or (.value.force_translucent | type) == "boolean")) then "valid" else "invalid" end else "invalid" end), .key, (if (.value | type) == "string" then .value elif (.value | type) == "object" then (.value.sprite? // empty) else empty end)] | @tsv)' "$model_file") while IFS= read -r parent; do parent="$(strip_cr "$parent")" resolve_model "$ns" "$parent" done < <(jq -r '.parent? // empty' "$model_file") while IFS= read -r over_model; do over_model="$(strip_cr "$over_model")" resolve_model "$ns" "$over_model" done < <(jq -r '.overrides[]?.model? // empty' "$model_file") done < <(find "$ROOT/assets" -type f -path '*/models/*.json' -print0 2>/dev/null) echo "Checking item definition model references..." while IFS= read -r -d '' item_file; do rel="${item_file#"$ROOT/assets/"}" ns="${rel%%/*}" while IFS= read -r model_ref; do model_ref="$(strip_cr "$model_ref")" resolve_model "$ns" "$model_ref" done < <(jq -r '.. | objects | select(.type? == "minecraft:model" and (.model? | type) == "string") | .model' "$item_file") while IFS= read -r custom_model_data_case; do custom_model_data_case="$(strip_cr "$custom_model_data_case")" [[ "$custom_model_data_case" == 'valid' ]] || fail "minecraft:custom_model_data select cases must use a string or a non-empty list of strings in ${item_file#$ROOT/}" done < <(jq -r '.. | objects | select(.type? == "minecraft:select" and .property? == "minecraft:custom_model_data") | .cases[]? | if ((.when? | type) == "string") or ((.when? | type) == "array" and (.when | length > 0) and all(.when[]?; type == "string")) then "valid" else "invalid" end' "$item_file") done < <(find "$ROOT/assets" -type f -path '*/items/*.json' -print0 2>/dev/null) while IFS= read -r -d '' blockstate_file; do rel="${blockstate_file#"$ROOT/assets/"}" ns="${rel%%/*}" while IFS= read -r model_ref; do model_ref="$(strip_cr "$model_ref")" resolve_model "$ns" "$model_ref" done < <(jq -r '( .variants? // {} | .. | objects | .model? // empty ), ( .multipart[]?.apply? | if type == "array" then .[]?.model? // empty else .model? // empty end )' "$blockstate_file") done < <(find "$ROOT/assets" -type f -path '*/blockstates/*.json' -print0 2>/dev/null) echo "Checking sounds.json references..." while IFS= read -r -d '' sounds_file; do rel="${sounds_file#"$ROOT/assets/"}" ns="${rel%%/*}" while IFS=$'\t' read -r sound_type sound_ref; do sound_type="$(strip_cr "$sound_type")" sound_ref="$(strip_cr "$sound_ref")" resolve_sound "$ns" "$sound_type" "$sound_ref" "$sounds_file" done < <(jq -r '.. | objects | select(has("sounds")) | .sounds[]? | if type == "string" then ["file", .] else [(.type // "file"), (.name // empty)] end | @tsv' "$sounds_file") done < <(find "$ROOT/assets" -type f -name 'sounds.json' -print0 2>/dev/null) echo "Checking font provider file references..." while IFS= read -r -d '' font_file; do rel="${font_file#"$ROOT/assets/"}" ns="${rel%%/*}" while IFS= read -r font_ref; do font_ref="$(strip_cr "$font_ref")" [[ -z "$font_ref" ]] && continue if [[ "$font_ref" == *:* ]]; then target_ns="${font_ref%%:*}" target_path="${font_ref#*:}" else target_ns="$ns" target_path="$font_ref" fi target="$ROOT/assets/$target_ns/textures/$target_path" if [[ -f "$target" ]]; then pass "font texture exists: $target_ns:$target_path" elif [[ "$target_ns" != "$ns" ]]; then warn "external font texture not locally verifiable: $target_ns:$target_path" else fail "missing font texture: $target_ns:$target_path (expected ${target#$ROOT/})" fi done < <(jq -r '.providers[]? | .file? // empty' "$font_file") done < <(find "$ROOT/assets" -type f -path '*/font/*.json' -print0 2>/dev/null) echo "Checking .png.mcmeta pairs..." while IFS= read -r -d '' mcmeta_file; do check_json "$mcmeta_file" png_file="${mcmeta_file%.mcmeta}" if [[ -f "$png_file" ]]; then pass "animation pair exists: ${mcmeta_file#$ROOT/}" else fail "orphan .png.mcmeta: ${mcmeta_file#$ROOT/} (missing ${png_file#$ROOT/})" fi done < <(find "$ROOT/assets" -type f -name '*.png.mcmeta' -print0 2>/dev/null) echo "" if [[ "$FAILURES" -gt 0 ]]; then echo "$FAIL resource-pack validation failed with $FAILURES error(s) and $WARNINGS warning(s)" exit 1 fi if [[ "$STRICT" -eq 1 && "$WARNINGS" -gt 0 ]]; then echo "$FAIL resource-pack validation strict mode failed on $WARNINGS warning(s)" exit 1 fi echo "$PASS resource-pack validation passed with $WARNINGS warning(s)"
-
-
SKILL.md 12.6 KB
--- name: minecraft-resource-pack description: "Create and debug Minecraft 26.x and 1.21.x resource packs, including pack metadata, textures, models, blockstates, item definitions, sounds, fonts, animations, and shaders. Use for client-side visual or audio assets without gameplay code." --- # Minecraft Resource Pack Skill ## What Is a Resource Pack? A resource pack is a folder (or `.zip`) that overrides or adds Minecraft's visual and audio assets: textures, models, sounds, language files, and fonts. No Java or mod loader is required for those vanilla assets. ### Routing Boundaries - `Use when`: the deliverable is visual/audio assets (textures, models, sounds, fonts) in resource-pack format. - `Do not use when`: the task requires gameplay logic or runtime behavior changes (use `minecraft-datapack`, `minecraft-plugin-dev`, or `minecraft-modding`). - `Do not use when`: the task is server infrastructure/runtime administration (`minecraft-server-admin`). --- ## Pack Metadata | Minecraft Version | Preferred `pack` metadata | |-------------------|---------------------------| | 1.21 / 1.21.1 | `pack_format: 34` | | 1.21.2 / 1.21.3 | `pack_format: 42` | | 1.21.4 | `pack_format: 46` | | 1.21.5 | `pack_format: 55` | | 1.21.6 | `pack_format: 63` | | 1.21.7 / 1.21.8 | `pack_format: 64` | | 1.21.9 / 1.21.10 | `min_format: [69, 0]`, `max_format: [69, 0]` | | 1.21.11 | `min_format: [75, 0]`, `max_format: [75, 0]` | | 26.1 | `min_format: [84, 0]`, `max_format: [84, 0]` | | 26.2 | `min_format: [88, 0]`, `max_format: [88, 0]` | Use legacy `pack_format` only for resource formats below 65 (through 1.21.8). Resource format 65 and later require both `min_format` and `max_format`; use `[major, minor]` for exact patch targeting, including `.0` such as `[84, 0]`. An integer or `[major]` `min_format` means `[major, 0]`; an integer or `[major]` `max_format` permits every minor version on that major line. Do not write decimal JSON numbers. A legacy-only pack can use its integer `pack_format` alone. A range whose `min_format` includes a legacy format needs integer `pack_format` and `supported_formats`; do not include `supported_formats` for a modern-only range. --- ## Directory Layout ``` my-pack/ ├── pack.mcmeta ├── pack.png ← 64×64 icon (optional) └── assets/ └── minecraft/ ← override vanilla (or <namespace>/ for new packs) ├── models/ │ ├── block/ │ │ └── stone.json │ └── item/ │ └── diamond_sword.json ├── items/ ← 1.21.4+ item model definitions │ └── diamond_sword.json ├── blockstates/ │ └── stone.json ├── textures/ │ ├── block/ │ │ └── stone.png │ ├── item/ │ │ └── diamond_sword.png │ ├── gui/ │ │ └── sprites/ │ │ └── my_sprite.png │ └── entity/ │ └── zombie/ │ └── zombie.png ├── sounds/ │ └── custom/ │ └── my_sound.ogg ├── sounds.json ├── font/ │ └── default.json └── lang/ └── en_us.json ``` Client-mod-specific files such as OptiFine CIT and Iris shader packs use their own formats. Read [conditional assets](references/conditional-assets.md) before adding them. --- ## `pack.mcmeta` ### 1.21.8 and earlier ```json { "pack": { "pack_format": 64, "description": "My Custom Resource Pack v1.0" } } ``` ### 1.21.9 / 1.21.10 ```json { "pack": { "min_format": [69, 0], "max_format": [69, 0], "description": "My Custom Resource Pack v1.0" } } ``` ### 1.21.11 ```json { "pack": { "min_format": [75, 0], "max_format": [75, 0], "description": "My Custom Resource Pack v1.0" } } ``` ### 26.2 ```json { "pack": { "min_format": [88, 0], "max_format": [88, 0], "description": "My Custom Resource Pack v1.0" } } ``` --- ## Block Models ### `assets/minecraft/models/block/my_cube.json` Full cube — all six faces use the same texture: ```json { "parent": "minecraft:block/cube_all", "textures": { "all": "minecraft:block/stone" } } ``` Column block (like logs): ```json { "parent": "minecraft:block/cube_column", "textures": { "end": "mypack:block/my_pillar_top", "side": "mypack:block/my_pillar_side" } } ``` Different sides: ```json { "parent": "minecraft:block/cube", "textures": { "up": "mypack:block/my_block_top", "down": "mypack:block/my_block_bottom", "north": "mypack:block/my_block_side", "south": "mypack:block/my_block_side", "east": "mypack:block/my_block_side", "west": "mypack:block/my_block_side", "particle": "mypack:block/my_block_side" } } ``` Cross model (flowers, plants): ```json { "parent": "minecraft:block/cross", "textures": { "cross": "mypack:block/my_flower" } } ``` ### Custom geometry (elements) ```json { "credit": "Custom model", "ambientocclusion": true, "textures": { "0": "mypack:block/panel", "particle": "mypack:block/panel" }, "elements": [ { "from": [0, 0, 7], "to": [16, 16, 9], "faces": { "north": { "texture": "#0", "uv": [0, 0, 16, 16] }, "south": { "texture": "#0", "uv": [0, 0, 16, 16] } } } ], "display": { "thirdperson_righthand": { "rotation": [75, 45, 0], "translation": [0, 2.5, 0], "scale": [0.375, 0.375, 0.375] } } } ``` > `from` and `to` are in 1/16th block units (0–16). `uv` is `[x1, y1, x2, y2]` in 0–16 units. --- ## Item Models For **1.21.4 and later**, use item definitions in `assets/<namespace>/items/`. For **1.21.3 and earlier**, use the legacy model `overrides` array. Read [conditional assets](references/conditional-assets.md) for the string-based current `custom_model_data` selector and the legacy numeric predicate; their values are not interchangeable. --- ## Blockstate Definitions ### Simple block (no variants) ```json { "variants": { "": { "model": "mypack:block/my_block" } } } ``` ### Facing block (4 rotations) ```json { "variants": { "facing=north": { "model": "mypack:block/my_block" }, "facing=south": { "model": "mypack:block/my_block", "y": 180 }, "facing=east": { "model": "mypack:block/my_block", "y": 90 }, "facing=west": { "model": "mypack:block/my_block", "y": 270 } } } ``` ### Random texture (multipart) ```json { "variants": { "": [ { "model": "minecraft:block/grass_block", "weight": 3 }, { "model": "minecraft:block/grass_block_2" } ] } } ``` ### Multipart (slabs, fences, walls) ```json { "multipart": [ { "apply": { "model": "mypack:block/my_slab_bottom" }, "when": { "type": "bottom" } }, { "apply": { "model": "mypack:block/my_slab_top" }, "when": { "type": "top" } }, { "apply": { "model": "mypack:block/my_block" }, "when": { "type": "double" } } ] } ``` --- ## Textures - Format: **PNG**, RGBA (32-bit) - Standard block/item size: **16×16 px** - Textures can be larger (32×32, 64×64) — Minecraft scales them, but stick to powers of 2 - Animation requires height = N × width (e.g., 16×64 for 4 frames) - Place block textures in `assets/<namespace>/textures/block/` - Place item textures in `assets/<namespace>/textures/item/` - All textures are referenced without the `.png` extension in JSON ### Animated texture MCMETA `assets/minecraft/textures/block/fire_0.png.mcmeta`: ```json { "animation": { "frametime": 2, "frames": [0, 1, 2, 3, 4, 5, 6, 7] } } ``` If `frames` is omitted, all frames play sequentially. `frametime` is in game ticks (default 1). ### GUI sprites (1.20.2+) Place sprites at `assets/minecraft/textures/gui/sprites/<category>/<name>.png`. Reference them with `<category>/<name>` in code/JSON. ### 26.1 block-model texture entries For 26.1, a block-model `textures` entry may remain a sprite string or use an object. The object must have a string `sprite`; `force_translucent`, when set, must be a boolean. ```json { "textures": { "all": { "sprite": "mypack:block/frosted_panel", "force_translucent": true } } } ``` Both forms identify a sprite without `.png`. Use `force_translucent` only when the geometry must render in the translucent pass despite its sprite pixels. --- ## Sounds An event's namespace comes from the namespace containing `sounds.json`; a sound entry's `name` identifies the sound-file namespace. Sound files are Vorbis `.ogg` under `assets/<namespace>/sounds/`. Read [conditional assets](references/conditional-assets.md) for a correct event example, aliases, sound-source selection, and replacement behavior. --- ## Language Files `assets/minecraft/lang/en_us.json`: ```json { "block.mypack.my_block": "My Custom Block", "item.mypack.my_item": "Magic Wand", "entity.mypack.my_mob": "Forest Guardian", "death.attack.mypack.laser": "%1$s was zapped by %2$s" } ``` - Use the exact translation key format for your mod/datapack namespace - File name is the locale code (e.g., `fr_fr.json`, `de_de.json`) - Always provide `en_us.json` as the primary fallback --- ## Fonts Read [conditional assets](references/conditional-assets.md) for bitmap provider layout and private-use icon guidance. --- ## OptiFine and shaders OptiFine CIT and Iris shader packs have client-mod-specific formats. Read [conditional assets](references/conditional-assets.md) before adding either; they are not portable vanilla resource-pack features. --- ## Installation ```bash # Singleplayer: place in ~/.minecraft/resourcepacks/my-pack/ # or ~/.minecraft/resourcepacks/my-pack.zip # Server-side (forces on clients): # Set in server.properties: resource-pack=https://example.com/my-pack.zip resource-pack-sha1=<sha1 hash> resource-pack-prompt={"text":"Required pack","color":"gold"} ``` --- ## Common Issues | Problem | Cause | Fix | |---------|-------|-----| | Model not showing | Wrong JSON path or syntax error | Check `assets/<namespace>/models/` path; validate JSON | | Black/pink checkerboard | Texture path wrong or missing | Check `textures/` path, file extension not in JSON | | Blockstate not applying | Wrong state property name | Use F3 to inspect block state; use `/data get block <x> <y> <z>` for block-entity NBT | | Animation not working | Wrong MCMETA location | Must be same folder as texture, named `texture.png.mcmeta` | | Custom sound not playing | Not in `sounds.json` | Register sound event in `sounds.json`, match namespace | | Pack not loading | Wrong `pack_format` or `min_format` / `max_format` values | Update `pack.mcmeta` for the exact 1.21.x patch | ## Validator Script Use the bundled validator script before shipping a resource-pack update: Unbundled references to another namespace's models, textures, sounds, or fonts are warnings requiring runtime verification with the dependency present. Missing files in the current namespace fail; strict mode also fails unresolved warnings. ```bash # Run from the installed skill directory (for example `.claude/skills/minecraft-resource-pack`): ./scripts/validate-resource-pack.sh --root /path/to/resource-pack # Strict mode treats warnings as failures: ./scripts/validate-resource-pack.sh --root /path/to/resource-pack --strict ``` What it checks: - JSON validity for `pack.mcmeta` and `assets/**/*.json` - Legacy versus modern `pack.mcmeta` field shape, including integer, `[major]`, and `[major, minor]` versions - Model/blockstate/font/sounds references resolve to real files - Current `custom_model_data` select cases use strings; 26.1 texture objects use a string `sprite` and optional boolean `force_translucent` - Same-namespace `type: "event"` sound aliases name an event in that `sounds.json` - Every `*.png.mcmeta` has a matching `*.png` The validator cannot resolve a sound event owned by another namespace, or prove that a command or mod code invokes an event with the intended sound source. Those cases warn and require an exact-client runtime check. --- ## References - [Mojang: Java Edition 1.21.4](https://www.minecraft.net/en-us/article/minecraft-java-edition-1-21-4) - [Mojang: Java Edition 1.21.9](https://www.minecraft.net/en-us/article/minecraft-java-edition-1-21-9) - [Mojang: Java Edition 26.1](https://www.minecraft.net/en-us/article/minecraft-java-edition-26-1) - [NeoForge 1.21.8: Sounds](https://docs.neoforged.net/docs/1.21.8/resources/client/sounds/) - Misode's model viewer: https://misode.github.io/ - OptiFine CIT guide: https://optifine.readthedocs.io/cit.html
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.