1 Commits
Author SHA1 Message Date
root df3b190c8c Complete Bedrock resource pack conversion
Build and release Bedrock pack / release (push) Successful in 6s
2026-08-02 08:27:33 +02:00
3 changed files with 166 additions and 10 deletions
+17 -3
View File
@@ -9,8 +9,9 @@ This repository converts the **client-side Java resource pack assets** from `Fri
Included: Included:
- Seven custom music-disc icons and their streamed OGG audio. - Seven custom music-disc icons and their streamed OGG audio.
- Geyser custom-item mappings for the Java `music_disc_13` stacks whose `custom_model_data.strings` values identify those discs. - Geyser custom-item mappings for the Java `music_disc_13` and `paper` stacks whose `custom_model_data.strings` values identify those seven models.
- The Java cloud texture, preserved at Bedrock's environment texture path. - The Java cloud texture, preserved at Bedrock's environment texture path.
- The original 64×64 pack icon.
- Bedrock manifest, pack icon, validation, packaging, and tagged-release automation. - Bedrock manifest, pack icon, validation, packaging, and tagged-release automation.
## Build and validate ## Build and validate
@@ -31,16 +32,29 @@ The build output is `build/Friends-CMP-Bedrock.mcpack`. The generated `build/` d
3. Enable Geyser custom content/items in the server configuration, then restart Geyser. 3. Enable Geyser custom content/items in the server configuration, then restart Geyser.
4. Test each Java datapack disc in a Bedrock client: its icon should match its title and its jukebox sound should stream. 4. Test each Java datapack disc in a Bedrock client: its icon should match its title and its jukebox sound should stream.
The mappings use the current Geyser custom-item mapping format (`format_version: 2`) and match the Java datapack's string values at `custom_model_data.strings[0]`. The mappings use the current Geyser custom-item mapping format (`format_version: 2`) and match the Java pack's string values at `custom_model_data.strings[0]` for both `music_disc_13` and `paper`.
## Limitations ## Conversion notes and limitations
The source repository contains seven 16×16 item textures, seven OGG record tracks,
one 1×1 cloud texture, a pack icon, Java item-model selectors, and a Java datapack.
The textures and audio are copied byte-for-byte; Java's `generated` item models are
represented by Bedrock atlas entries and Geyser custom-item icon mappings.
- Bedrock does not understand Java `assets/` model JSON or Java item-model dispatch files. Those files are translated into Bedrock icon entries and Geyser mappings rather than copied. - Bedrock does not understand Java `assets/` model JSON or Java item-model dispatch files. Those files are translated into Bedrock icon entries and Geyser mappings rather than copied.
- The Java `clock.json` and `compass.json` selectors only select vanilla/Java model assets. Their referenced vanilla models are not part of the pack, and Bedrock has no equivalent for Java's item-model dispatch tree in a resource-only pack; those selectors are intentionally omitted. The seven `paper.json` custom-model-data cases are represented by the Geyser `minecraft:paper` mappings instead of being copied as Java JSON.
- The Java source has no custom fonts, block/entity models, GUI textures, particles, animations, or language files to convert.
- Resource packs cannot implement Java datapack logic. Giving items, jukebox components, predicates, functions, recipes, and other server behavior remain Java-side responsibilities. - Resource packs cannot implement Java datapack logic. Giving items, jukebox components, predicates, functions, recipes, and other server behavior remain Java-side responsibilities.
- The Bedrock pack alone cannot distinguish multiple custom icons that all use `music_disc_13`; the Geyser mapping file is required for that per-stack visual mapping. If the server uses an older Geyser build that does not support v2 mappings or string predicates, the custom icons cannot be selected reliably without changing the server-side item data/mapping approach. - The Bedrock pack alone cannot distinguish multiple custom icons that all use `music_disc_13`; the Geyser mapping file is required for that per-stack visual mapping. If the server uses an older Geyser build that does not support v2 mappings or string predicates, the custom icons cannot be selected reliably without changing the server-side item data/mapping approach.
- Bedrock's resource-pack sound catalog exposes the converted sound events, but custom Java jukebox behavior is still dependent on Geyser/server support for the mapped item. This repository does not modify the Java datapack. - Bedrock's resource-pack sound catalog exposes the converted sound events, but custom Java jukebox behavior is still dependent on Geyser/server support for the mapped item. This repository does not modify the Java datapack.
- No Java-only `assets/minecraft/items/*.json`, models, `pack.mcmeta`, language files, or datapack files are included in the `.mcpack`. - No Java-only `assets/minecraft/items/*.json`, models, `pack.mcmeta`, language files, or datapack files are included in the `.mcpack`.
The pack is a resource pack, not a standalone Bedrock behavior pack. For the seven
custom discs to retain their Java appearance and behavior through Geyser, install
both the `.mcpack` and `geyser/mappings.json`, and keep the Java datapack installed
on the server. A Bedrock client cannot independently recreate the Java datapack's
custom-model-data and `jukebox_playable` logic.
## Releases ## Releases
Tag pushes matching `v*` run `.github/workflows/release.yml`, validate the pack, build `Friends-CMP-Bedrock.mcpack`, and attach both the pack and `geyser/mappings.json` to the GitHub Release. The mapping file is released alongside the pack because Geyser needs it to select the per-disc icons. Tag pushes matching `v*` run `.github/workflows/release.yml`, validate the pack, build `Friends-CMP-Bedrock.mcpack`, and attach both the pack and `geyser/mappings.json` to the GitHub Release. The mapping file is released alongside the pack because Geyser needs it to select the per-disc icons.
+106
View File
@@ -106,6 +106,112 @@
} }
] ]
} }
],
"minecraft:paper": [
{
"type": "group",
"model": "minecraft:item/paper",
"definitions": [
{
"bedrock_identifier": "friends_cmp:interior_shop0",
"display_name": "Interior Shop",
"bedrock_options": {
"icon": "friends_cmp:interior_shop0",
"creative_category": "items"
},
"predicate": {
"type": "match",
"property": "custom_model_data",
"value": "interior_shop0",
"index": 0
}
},
{
"bedrock_identifier": "friends_cmp:pawn_shop1",
"display_name": "Pawn Shop",
"bedrock_options": {
"icon": "friends_cmp:pawn_shop1",
"creative_category": "items"
},
"predicate": {
"type": "match",
"property": "custom_model_data",
"value": "pawn_shop1",
"index": 0
}
},
{
"bedrock_identifier": "friends_cmp:food_mart2",
"display_name": "Food Mart",
"bedrock_options": {
"icon": "friends_cmp:food_mart2",
"creative_category": "items"
},
"predicate": {
"type": "match",
"property": "custom_model_data",
"value": "food_mart2",
"index": 0
}
},
{
"bedrock_identifier": "friends_cmp:hat_shop3",
"display_name": "Hat Shop",
"bedrock_options": {
"icon": "friends_cmp:hat_shop3",
"creative_category": "items"
},
"predicate": {
"type": "match",
"property": "custom_model_data",
"value": "hat_shop3",
"index": 0
}
},
{
"bedrock_identifier": "friends_cmp:clothing_shop4",
"display_name": "Clothing Shop",
"bedrock_options": {
"icon": "friends_cmp:clothing_shop4",
"creative_category": "items"
},
"predicate": {
"type": "match",
"property": "custom_model_data",
"value": "clothing_shop4",
"index": 0
}
},
{
"bedrock_identifier": "friends_cmp:jump_up5",
"display_name": "Jump Up",
"bedrock_options": {
"icon": "friends_cmp:jump_up5",
"creative_category": "items"
},
"predicate": {
"type": "match",
"property": "custom_model_data",
"value": "jump_up5",
"index": 0
}
},
{
"bedrock_identifier": "friends_cmp:city_under_siege6",
"display_name": "City under siege",
"bedrock_options": {
"icon": "friends_cmp:city_under_siege6",
"creative_category": "items"
},
"predicate": {
"type": "match",
"property": "custom_model_data",
"value": "city_under_siege6",
"index": 0
}
}
]
}
] ]
} }
} }
+43 -7
View File
@@ -1,26 +1,62 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""Validate pack metadata, required assets, mappings, and archive contents.""" """Validate metadata, references, and required binary assets in the pack."""
import json import json
import struct
from pathlib import Path from pathlib import Path
ROOT = Path(__file__).resolve().parents[1] ROOT = Path(__file__).resolve().parents[1]
def load(path): def load(path):
with path.open(encoding="utf-8") as stream: with path.open(encoding="utf-8") as stream:
return json.load(stream) return json.load(stream)
def assert_png(path, expected_size=None):
data = path.read_bytes()
assert data.startswith(b"\x89PNG\r\n\x1a\n"), f"not a PNG: {path}"
width, height = struct.unpack(">II", data[16:24])
if expected_size:
assert (width, height) == expected_size, f"unexpected PNG size for {path}: {(width, height)}"
manifest = load(ROOT / "manifest.json") manifest = load(ROOT / "manifest.json")
assert manifest["format_version"] == 2 assert manifest["format_version"] == 2
assert manifest["header"]["uuid"] != manifest["modules"][0]["uuid"] assert manifest["header"]["uuid"] != manifest["modules"][0]["uuid"]
assert manifest["modules"][0]["type"] == "resources" assert manifest["modules"][0]["type"] == "resources"
assert len(manifest["header"]["version"]) == 3 assert len(manifest["header"]["version"]) == 3
assert len(set(manifest["header"]["uuid"] for _ in [0])) == 1
assert manifest["header"]["min_engine_version"] >= [1, 21, 0]
assert_png(ROOT / "pack_icon.png", (64, 64))
textures = load(ROOT / "textures/item_texture.json")["texture_data"] textures = load(ROOT / "textures/item_texture.json")["texture_data"]
sound_defs = load(ROOT / "sounds/sound_definitions.json")["sound_definitions"] sound_defs = load(ROOT / "sounds/sound_definitions.json")["sound_definitions"]
mappings = load(ROOT / "geyser/mappings.json") mappings = load(ROOT / "geyser/mappings.json")
names = ("interior_shop0", "pawn_shop1", "food_mart2", "hat_shop3", "clothing_shop4", "jump_up5", "city_under_siege6") names = (
"interior_shop0", "pawn_shop1", "food_mart2", "hat_shop3",
"clothing_shop4", "jump_up5", "city_under_siege6",
)
assert set(textures) == {f"friends_cmp:{name}" for name in names} assert set(textures) == {f"friends_cmp:{name}" for name in names}
assert len(sound_defs) == len(names) assert len(sound_defs) == len(names)
for entry in textures.values(): for name in names:
assert (ROOT / (entry["textures"] + ".png")).is_file() texture = textures[f"friends_cmp:{name}"]
for entry in sound_defs.values(): texture_path = ROOT / (texture["textures"] + ".png")
assert (ROOT / (entry["sounds"][0]["name"] + ".ogg")).is_file() assert texture_path.is_file(), texture_path
assert_png(texture_path, (16, 16))
sound = sound_defs[f"music_disc.{name}"]
sound_path = ROOT / (sound["sounds"][0]["name"] + ".ogg")
assert sound_path.is_file(), sound_path
assert sound_path.read_bytes()[:4] == b"OggS", f"not OGG: {sound_path}"
assert mappings["format_version"] == 2 assert mappings["format_version"] == 2
assert len(mappings["items"]["minecraft:music_disc_13"][0]["definitions"]) == len(names) for java_item in ("minecraft:music_disc_13", "minecraft:paper"):
definitions = mappings["items"][java_item][0]["definitions"]
assert len(definitions) == len(names), java_item
assert {entry["bedrock_identifier"] for entry in definitions} == set(textures)
for entry in definitions:
assert entry["bedrock_options"]["icon"] in textures
assert entry["predicate"]["property"] == "custom_model_data"
assert entry["predicate"]["value"] in names
assert_png(ROOT / "textures/environment/clouds.png", (1, 1))
print("Bedrock pack validation passed") print("Bedrock pack validation passed")