commit f955d4321f14206ca00928af1c284dab6f4c4fe6 Author: root Date: Fri Jul 31 21:04:30 2026 +0200 Add Friends CMP Bedrock resource pack conversion diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..47aeb9e --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,32 @@ +name: Build and release Bedrock pack + +on: + push: + tags: + - 'v*' + workflow_dispatch: + +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Validate and build + run: | + python3 scripts/validate_pack.py + python3 scripts/build_pack.py + + - name: Create GitHub release + uses: softprops/action-gh-release@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + generate_release_notes: true + files: | + build/Friends-CMP-Bedrock.mcpack + geyser/mappings.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..880ac59 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build/ +.DS_Store diff --git a/README.md b/README.md new file mode 100644 index 0000000..fc7b16a --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +# Friends CMP Bedrock Resource Pack + +Bedrock resource pack assets for Friends CMP when Java players and Bedrock players meet through Geyser. + +## Scope + +This repository converts the **client-side Java resource pack assets** from `Friends-CMP-2.0` only. The Java repository's `data/` datapack is intentionally not copied or recreated; it remains installed on the Java server and is outside the scope of a Bedrock resource pack. + +Included: + +- 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. +- The Java cloud texture, preserved at Bedrock's environment texture path. +- Bedrock manifest, pack icon, validation, packaging, and tagged-release automation. + +## Build and validate + +Requires Python 3.9+ and no third-party packages: + +```sh +python3 scripts/validate_pack.py +python3 scripts/build_pack.py +``` + +The build output is `build/Friends-CMP-Bedrock.mcpack`. The generated `build/` directory is ignored and should not be committed. + +## Geyser installation + +1. Copy `build/Friends-CMP-Bedrock.mcpack` to Geyser's `packs/` directory (or install it through the server's Bedrock resource-pack mechanism). +2. Copy `geyser/mappings.json` to Geyser's `custom_mappings/` directory. +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. + +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]`. + +## Limitations + +- 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. +- 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. +- 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`. + +## 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. diff --git a/geyser/mappings.json b/geyser/mappings.json new file mode 100644 index 0000000..d8fa4d2 --- /dev/null +++ b/geyser/mappings.json @@ -0,0 +1,111 @@ +{ + "format_version": 2, + "items": { + "minecraft:music_disc_13": [ + { + "type": "group", + "model": "minecraft:item/music_disc_13", + "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 + } + } + ] + } + ] + } +} diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..3d00078 --- /dev/null +++ b/manifest.json @@ -0,0 +1,22 @@ +{ + "format_version": 2, + "header": { + "name": "Friends CMP", + "description": "Friends CMP client assets for Bedrock/Geyser", + "uuid": "7db1c3b5-d7f3-4ff0-b1bc-12d51f90c08d", + "version": [1, 0, 0], + "min_engine_version": [1, 21, 0] + }, + "modules": [ + { + "description": "Friends CMP resource pack", + "type": "resources", + "uuid": "fa2dbac4-7c8a-4d70-a67c-0fbf6c8f24da", + "version": [1, 0, 0] + } + ], + "metadata": { + "authors": ["FriendsMC"], + "url": "https://github.com/FriendsMC/Friends-CMP-2.0-Bedrock" + } +} diff --git a/pack_icon.png b/pack_icon.png new file mode 100644 index 0000000..34a1ca3 Binary files /dev/null and b/pack_icon.png differ diff --git a/scripts/build_pack.py b/scripts/build_pack.py new file mode 100755 index 0000000..2911956 --- /dev/null +++ b/scripts/build_pack.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +"""Build tracked Bedrock resource-pack files into a .mcpack archive.""" +from pathlib import Path +from zipfile import ZIP_DEFLATED, ZipFile +ROOT = Path(__file__).resolve().parents[1] +OUT = ROOT / "build" / "Friends-CMP-Bedrock.mcpack" +EXCLUDED = {".git", "build", ".github", "scripts", "geyser", "README.md", ".gitignore"} +OUT.parent.mkdir(exist_ok=True) +with ZipFile(OUT, "w", ZIP_DEFLATED) as archive: + for path in sorted(ROOT.rglob("*")): + if path.is_file() and path.relative_to(ROOT).parts[0] not in EXCLUDED: + archive.write(path, path.relative_to(ROOT).as_posix()) +print(OUT) diff --git a/scripts/validate_pack.py b/scripts/validate_pack.py new file mode 100755 index 0000000..4243945 --- /dev/null +++ b/scripts/validate_pack.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +"""Validate pack metadata, required assets, mappings, and archive contents.""" +import json +from pathlib import Path +ROOT = Path(__file__).resolve().parents[1] +def load(path): + with path.open(encoding="utf-8") as stream: + return json.load(stream) +manifest = load(ROOT / "manifest.json") +assert manifest["format_version"] == 2 +assert manifest["header"]["uuid"] != manifest["modules"][0]["uuid"] +assert manifest["modules"][0]["type"] == "resources" +assert len(manifest["header"]["version"]) == 3 +textures = load(ROOT / "textures/item_texture.json")["texture_data"] +sound_defs = load(ROOT / "sounds/sound_definitions.json")["sound_definitions"] +mappings = load(ROOT / "geyser/mappings.json") +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 len(sound_defs) == len(names) +for entry in textures.values(): + assert (ROOT / (entry["textures"] + ".png")).is_file() +for entry in sound_defs.values(): + assert (ROOT / (entry["sounds"][0]["name"] + ".ogg")).is_file() +assert mappings["format_version"] == 2 +assert len(mappings["items"]["minecraft:music_disc_13"][0]["definitions"]) == len(names) +print("Bedrock pack validation passed") diff --git a/sounds/records/music_disc_city_under_siege6.ogg b/sounds/records/music_disc_city_under_siege6.ogg new file mode 100644 index 0000000..4f84fa3 Binary files /dev/null and b/sounds/records/music_disc_city_under_siege6.ogg differ diff --git a/sounds/records/music_disc_clothing_shop4.ogg b/sounds/records/music_disc_clothing_shop4.ogg new file mode 100644 index 0000000..5a8dacb Binary files /dev/null and b/sounds/records/music_disc_clothing_shop4.ogg differ diff --git a/sounds/records/music_disc_food_mart2.ogg b/sounds/records/music_disc_food_mart2.ogg new file mode 100644 index 0000000..4b80dd1 Binary files /dev/null and b/sounds/records/music_disc_food_mart2.ogg differ diff --git a/sounds/records/music_disc_hat_shop3.ogg b/sounds/records/music_disc_hat_shop3.ogg new file mode 100644 index 0000000..10f6b61 Binary files /dev/null and b/sounds/records/music_disc_hat_shop3.ogg differ diff --git a/sounds/records/music_disc_interior_shop0.ogg b/sounds/records/music_disc_interior_shop0.ogg new file mode 100644 index 0000000..7b79af0 Binary files /dev/null and b/sounds/records/music_disc_interior_shop0.ogg differ diff --git a/sounds/records/music_disc_jump_up5.ogg b/sounds/records/music_disc_jump_up5.ogg new file mode 100644 index 0000000..e66edfb Binary files /dev/null and b/sounds/records/music_disc_jump_up5.ogg differ diff --git a/sounds/records/music_disc_pawn_shop1.ogg b/sounds/records/music_disc_pawn_shop1.ogg new file mode 100644 index 0000000..d2b589d Binary files /dev/null and b/sounds/records/music_disc_pawn_shop1.ogg differ diff --git a/sounds/sound_definitions.json b/sounds/sound_definitions.json new file mode 100644 index 0000000..4ff5980 --- /dev/null +++ b/sounds/sound_definitions.json @@ -0,0 +1,75 @@ +{ + "format_version": "1.20.20", + "sound_definitions": { + "music_disc.interior_shop0": { + "category": "record", + "max_distance": 64, + "sounds": [ + { + "name": "sounds/records/music_disc_interior_shop0", + "stream": true + } + ] + }, + "music_disc.pawn_shop1": { + "category": "record", + "max_distance": 64, + "sounds": [ + { + "name": "sounds/records/music_disc_pawn_shop1", + "stream": true + } + ] + }, + "music_disc.food_mart2": { + "category": "record", + "max_distance": 64, + "sounds": [ + { + "name": "sounds/records/music_disc_food_mart2", + "stream": true + } + ] + }, + "music_disc.hat_shop3": { + "category": "record", + "max_distance": 64, + "sounds": [ + { + "name": "sounds/records/music_disc_hat_shop3", + "stream": true + } + ] + }, + "music_disc.clothing_shop4": { + "category": "record", + "max_distance": 64, + "sounds": [ + { + "name": "sounds/records/music_disc_clothing_shop4", + "stream": true + } + ] + }, + "music_disc.jump_up5": { + "category": "record", + "max_distance": 64, + "sounds": [ + { + "name": "sounds/records/music_disc_jump_up5", + "stream": true + } + ] + }, + "music_disc.city_under_siege6": { + "category": "record", + "max_distance": 64, + "sounds": [ + { + "name": "sounds/records/music_disc_city_under_siege6", + "stream": true + } + ] + } + } +} diff --git a/textures/environment/clouds.png b/textures/environment/clouds.png new file mode 100644 index 0000000..507fdc1 Binary files /dev/null and b/textures/environment/clouds.png differ diff --git a/textures/item_texture.json b/textures/item_texture.json new file mode 100644 index 0000000..df697ee --- /dev/null +++ b/textures/item_texture.json @@ -0,0 +1,13 @@ +{ + "resource_pack_name": "friends_cmp", + "texture_name": "atlas.items", + "texture_data": { + "friends_cmp:interior_shop0": {"textures": "textures/items/interior_shop0"}, + "friends_cmp:pawn_shop1": {"textures": "textures/items/pawn_shop1"}, + "friends_cmp:food_mart2": {"textures": "textures/items/food_mart2"}, + "friends_cmp:hat_shop3": {"textures": "textures/items/hat_shop3"}, + "friends_cmp:clothing_shop4": {"textures": "textures/items/clothing_shop4"}, + "friends_cmp:jump_up5": {"textures": "textures/items/jump_up5"}, + "friends_cmp:city_under_siege6": {"textures": "textures/items/city_under_siege6"} + } +} diff --git a/textures/items/city_under_siege6.png b/textures/items/city_under_siege6.png new file mode 100644 index 0000000..fd3641f Binary files /dev/null and b/textures/items/city_under_siege6.png differ diff --git a/textures/items/clothing_shop4.png b/textures/items/clothing_shop4.png new file mode 100644 index 0000000..e4ad6a0 Binary files /dev/null and b/textures/items/clothing_shop4.png differ diff --git a/textures/items/food_mart2.png b/textures/items/food_mart2.png new file mode 100644 index 0000000..e4ad6a0 Binary files /dev/null and b/textures/items/food_mart2.png differ diff --git a/textures/items/hat_shop3.png b/textures/items/hat_shop3.png new file mode 100644 index 0000000..e4ad6a0 Binary files /dev/null and b/textures/items/hat_shop3.png differ diff --git a/textures/items/interior_shop0.png b/textures/items/interior_shop0.png new file mode 100644 index 0000000..e4ad6a0 Binary files /dev/null and b/textures/items/interior_shop0.png differ diff --git a/textures/items/jump_up5.png b/textures/items/jump_up5.png new file mode 100644 index 0000000..fd3641f Binary files /dev/null and b/textures/items/jump_up5.png differ diff --git a/textures/items/pawn_shop1.png b/textures/items/pawn_shop1.png new file mode 100644 index 0000000..e4ad6a0 Binary files /dev/null and b/textures/items/pawn_shop1.png differ