From c043f481961406d252f4d3df8c0901eba6d6e905 Mon Sep 17 00:00:00 2001 From: Kas-tle <26531652+Kas-tle@users.noreply.github.com> Date: Sat, 7 May 2022 11:38:57 -0700 Subject: [PATCH] Create converter.sh --- converter.sh | 857 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 857 insertions(+) create mode 100644 converter.sh diff --git a/converter.sh b/converter.sh new file mode 100644 index 0000000..c0c62af --- /dev/null +++ b/converter.sh @@ -0,0 +1,857 @@ +#!/usr/bin/env bash +: ${1?'Please specify an input resource pack in the same directory as the script (e.g. ./converter.sh MyResourcePack.zip)'} + +# define color placeholders +C_RED='\e[31m' +C_GREEN='\e[32m' +C_YELLOW='\e[33m' +C_BLUE='\e[36m' +C_GRAY='\e[37m' +C_CLOSE='\e[m' + +# status message function +status_message () { + case $1 in + "completion") + printf "${C_GREEN}[+] ${C_GRAY}${2}${C_CLOSE}\n" + ;; + "process") + printf "${C_YELLOW}[•] ${C_GRAY}${2}${C_CLOSE}\n" + ;; + "critical") + printf "${C_RED}[X] ${C_GRAY}${2}${C_CLOSE}\n" + ;; + "error") + printf "${C_RED}[ERROR] ${C_GRAY}${2}${C_CLOSE}\n" + ;; + "info") + printf "${C_BLUE}${2}${C_CLOSE}\n" + ;; + "plain") + printf "${C_GRAY}${2}${C_CLOSE}\n" + ;; + esac +} + +dependency_check () { + if command ${3} 2>/dev/null | grep -q "${4}"; then + status_message completion "Dependency ${1} satisfied" + else + status_message error "Dependency ${1} must be installed to proceed\nSee ${2}\nExiting script..." + exit 1 + fi +} + +user_input () { + status_message plain "${2} ${C_YELLOW}[${3}]\n" + read -p "${4}: " ${1} + echo +} + +# ensure input pack exists +if ! test -f "${1}"; then + status_message error "Input resource pack ${1} is not in this directory" + exit 1 +else + status_message process "Input file ${1} detected" +fi + +printf '\e[1;31m%-6s\e[m\n' " +███████████████████████████████████████████████████████████████████████████████ +████████████████████████ # # W A R N I N G # # ████████████████████████ +███████████████████████████████████████████████████████████████████████████████ +███ This script has been provided as is. If your resource pack does not ███ +███ entirely conform the vanilla resource specification, including but not ███ +███ limited to, missing textures, improper parenting, improperly defined ███ +███ predicates, and malformed JSON files, among other problems, there is a ███ +███ strong possibility this script will fail. Please remedy any potential ███ +███ resource pack formatting errors before attempting to make use of this ███ +███ converter. You have been warned. ███ +███████████████████████████████████████████████████████████████████████████████ +███████████████████████████████████████████████████████████████████████████████ +███████████████████████████████████████████████████████████████████████████████ +" + +read -p $'\e[37mTo acknowledge and continue, press enter. To exit, press Ctrl+C.:\e[0m + +' + +# ensure we have all the required dependencies +dependency_check "jq-1.6" "https://stedolan.github.io/jq/download/" "jq --version" "1.6" +dependency_check "sponge" "https://joeyh.name/code/moreutils/" "-v sponge" "" +dependency_check "imagemagick-7" "https://imagemagick.org/script/download.php" "magick --version" "ImageMagick 7." +dependency_check "spritesheet-js" "https://www.npmjs.com/package/spritesheet-js" "-v spritesheet-js" "" +status_message completion "All dependencies have been satisfied\n" + +# initial configuration +if [[ ${2} != default ]] +then + status_message info "This script will now ask some configuration questions. Default values are yellow. Simply press enter to use the defaults.\n" + user_input merge_input "Is there an existing bedrock pack in this directory with which you would like the output merged? (e.g. input.mcpack)" "null" "Input pack to merge" + user_input attachable_material "What material should we use for the attachables?" "entity_alphatest" "Attachable material" + user_input block_material "What material should we use for the blocks?" "alpha_test" "Block material" + user_input fallback_pack "From what URL should we download the fallback resource pack? (must be a direct link)\n Use 'none' if default resources are not needed." "null" "Fallback pack URL" +fi + +status_message plain " +Generating Bedrock 3D resource pack with settings: +${C_GRAY}Input pack to merge: ${C_BLUE}${merge_input:=null} +${C_GRAY}Attachable material: ${C_BLUE}${attachable_material:=entity_alphatest} +${C_GRAY}Block material: ${C_BLUE}${block_material:=alpha_test} +${C_GRAY}Fallback pack URL: ${C_BLUE}${fallback_pack:=null} +" + +# decompress our input pack +status_message process "Decompressing input pack" +unzip -q ${1} +status_message completion "Input pack decompressed" + +# get the current default textures and merge them with our rp +if [[ ${fallback_pack} != none ]] +then + status_message process "Now downloading the fallback resource pack:" +fi + +if [[ ${fallback_pack} = null ]] +then + printf "\e[3m\e[37m" + wget -nv --show-progress -O default_assets.zip https://github.com/InventivetalentDev/minecraft-assets/zipball/refs/tags/1.18.2 + printf "${C_CLOSE}" + status_message completion "Fallback resources downloaded" + root_folder=($(unzip -Z -1 default_assets.zip | head -1)) +fi + +if [[ ${fallback_pack} != null && ${fallback_pack} != none ]] +then + printf "\e[3m\e[37m" + wget -nv --show-progress -O default_assets.zip "${fallback_pack}" + status_message completion "Fallback resources downloaded" + root_folder="" +fi + +if [[ ${fallback_pack} != none ]] +then + mkdir ./defaultassetholding + unzip -q -d ./defaultassetholding default_assets.zip "${root_folder}assets/minecraft/textures/**/*" + status_message completion "Fallback resources decompressed" + cp -n -r "./defaultassetholding/${root_folder}assets/minecraft/textures"/* './assets/minecraft/textures/' + status_message completion "Fallback resources merged with target pack" + rm -rf defaultassetholding + rm -f default_assets.zip + status_message critical "Extraneous fallback resources deleted\n" +fi + +# generate a fallback texture +convert -size 16x16 xc:\#FFFFFF ./assets/minecraft/textures/0.png + +# setup our initial config +status_message process "Iterating through all vanilla associated model JSONs to generate initial predicate config\nOn a large pack, this may take some time...\n" + +if test -d "./assets/minecraft/models/item"; then confarg1="./assets/minecraft/models/item/*.json"; fi +if test -d "./assets/minecraft/models/block"; then confarg2="./assets/minecraft/models/block/*.json"; fi + + +jq -n '[inputs | {(input_filename | sub("(.+)/(?.*?).json"; .itemname)): .overrides?[]?}] | + +def maxdur($input): +({ + "carrot_on_a_stick": 25, + "golden_axe": 32, + "golden_hoe": 32, + "golden_pickaxe": 32, + "golden_shovel": 32, + "golden_sword": 32, + "wooden_axe": 59, + "wooden_hoe": 59, + "wooden_pickaxe":59, + "wooden_shovel":59, + "wooden_sword": 59, + "fishing_rod": 64, + "flint_and_steel": 64, + "warped_fungus_on_a_stick": 100, + "sparkler": 100, + "glow_stick": 100, + "stone_axe": 131, + "stone_hoe": 131, + "stone_pickaxe":131, + "stone_shovel":131, + "stone_sword": 131, + "shears": 238, + "iron_axe": 250, + "iron_hoe": 250, + "iron_pickaxe": 250, + "iron_shovel": 250, + "iron_sword": 250, + "trident": 250, + "crossbow": 326, + "shield": 336, + "bow": 384, + "elytra": 432, + "diamond_axe": 1561, + "diamond_hoe": 1561, + "diamond_pickaxe": 1561, + "diamond_shovel": 1561, + "diamond_sword": 1561, + "netherite_axe": 2031, + "netherite_hoe": 2031, + "netherite_pickaxe": 2031, + "netherite_shovel": 2031, + "netherite_sword": 2031 +} | .[$input] // 1) +; + +def namespace: +if contains(":") then sub("\\:(.+)"; "") else "minecraft" end +; + +[.[] | to_entries | map( select((.value.predicate.damage != null) or (.value.predicate.damaged != null) or (.value.predicate.custom_model_data != null)) | + (if .value.predicate.damage then (.value.predicate.damage * maxdur(.key) | round) else null end) as $damage + | (if .value.predicate.damaged == 0 then 1 else null end) as $unbreakable + | (if .value.predicate.custom_model_data then .value.predicate.custom_model_data else null end) as $custom_model_data | + { + "item": .key, + "nbt": ({ + "Damage": $damage, + "Unbreakable": $unbreakable, + "CustomModelData": $custom_model_data + }), + "path": ("./assets/" + (.value.model | namespace) + "/models/" + (.value.model | sub("(.*?)\\:"; "")) + ".json") + +}) | .[]] +| walk(if type == "object" then with_entries(select(.value != null)) else . end) +| to_entries | map( ((.value.geyserID = "gmdl_\(1+.key)" | .value.geometry = ("geometry.geysercmd." + "gmdl_\(1+.key)")) | .value)) +| INDEX(.geyserID) + +' ${confarg1} ${confarg2} | sponge config.json +status_message completion "Initial predicate config generated" + +# get a bash array of all model json files in our resource pack +status_message process "Generating an array of all model JSON files to crosscheck with our predicate config" +json_dir=($(find ./assets/**/models -type f -name '*.json')) + +# ensure all our reference files in config.json exist, and delete the entry if they do not +status_message critical "Removing config entries that do not have an associated JSON file in the pack" +jq ' + +def real_file($input): +($ARGS.positional | index($input) // null); + +map_values(if real_file(.path) != null then . else empty end) + +' config.json --args ${json_dir[@]} | sponge config.json + +# get a bash array of all our input models +status_message process "Creating a bash array for remaing models in our predicate config" +model_array=($(jq -r '.[].path' config.json)) + +# find initial parental information +status_message process "Doing an initial sweep for level 1 parentals" +jq -n ' + +[def namespace: if contains(":") then sub("\\:(.+)"; "") else "minecraft" end; + +inputs | { + "path": (input_filename), + "parent": ("./assets/" + (.parent | namespace) + "/models/" + ((.parent? // empty) | sub("(.*?)\\:"; "")) + ".json") + } +] + +' ${model_array[@]} | sponge parents.json + +# add initial parental information to config.json +status_message critical "Removing config entries with non-supported parentals\n" +jq -s ' + +. as $global | + +def intest($input_i): ($global | .[0] | map({(.path): .parent}) | add | .[$input_i]? // null); + +def gtest($input_g): +["./assets/minecraft/models/block/block.json", "./assets/minecraft/models/block/cube.json", "./assets/minecraft/models/block/cube_column.json", "./assets/minecraft/models/block/cube_directional.json", "./assets/minecraft/models/block/cube_mirrored.json", "./assets/minecraft/models/block/observer.json", "./assets/minecraft/models/block/orientable_with_bottom.json", "./assets/minecraft/models/block/piston_extended.json", "./assets/minecraft/models/block/redstone_dust_side.json", "./assets/minecraft/models/block/redstone_dust_side_alt.json", "./assets/minecraft/models/block/template_single_face.json", "./assets/minecraft/models/block/thin_block.json", "./assets/minecraft/models/builtin/entity.json", "./assets/minecraft/models/builtin/generated.json", "./assets/minecraft/models/item/bow.json", "./assets/minecraft/models/item/chest.json", "./assets/minecraft/models/item/crossbow.json", "./assets/minecraft/models/item/fishing_rod.json", "./assets/minecraft/models/item/generated.json", "./assets/minecraft/models/item/handheld.json", "./assets/minecraft/models/item/handheld_rod.json", "./assets/minecraft/models/item/template_skull.json"] +| index($input_g) // null; + +.[1] | map_values(. + ({"parent": (intest(.path) // null)} | if gtest(.parent) == null then . else empty end)) +| walk(if type == "object" then with_entries(select(.value != null)) else . end) + +' parents.json config.json | sponge config.json + + +# create our initial directories for bp & rp +status_message process "Generating initial directory strucutre for our bedrock packs" +mkdir -p ./target/rp/models/blocks/geysercmd && mkdir -p ./target/rp/textures/blocks/geysercmd && mkdir -p ./target/rp/attachables/geysercmd && mkdir -p ./target/rp/animations/geysercmd && mkdir -p ./target/bp/blocks/geysercmd + +# copy over our pack.png if we have one +if test -f "./pack.png"; then + cp ./pack.png ./target/rp/pack_icon.png && cp ./pack.png ./target/bp/pack_icon.png +fi + +# generate uuids for our manifests +uuid1=($(uuidgen)) +uuid2=($(uuidgen)) +uuid3=($(uuidgen)) +uuid4=($(uuidgen)) + +# get pack description if we have one +pack_desc=($(jq -r '(.pack.description // "Geyser 3D Items Resource Pack")' ./pack.mcmeta)) + +# generate rp manifest.json +status_message process "Generating resource pack manifest" +jq -c --arg pack_desc "${pack_desc}" --arg uuid1 "${uuid1}" --arg uuid2 "${uuid2}" -n ' +{ + "format_version": 2, + "header": { + "description": "Adds 3D items for use with a Geyser proxy", + "name": $pack_desc, + "uuid": ($uuid1 | ascii_downcase), + "version": [1, 0, 0], + "min_engine_version": [1, 16, 100] + }, + "modules": [ + { + "description": "Adds 3D items for use with a Geyser proxy", + "type": "resources", + "uuid": ($uuid2 | ascii_downcase), + "version": [1, 0, 0] + } + ] +} +' | sponge ./target/rp/manifest.json + +# generate bp manifest.json +status_message process "Generating behavior pack manifest" +jq -c --arg pack_desc "${pack_desc}" --arg uuid1 "${uuid1}" --arg uuid3 "${uuid3}" --arg uuid4 "${uuid4}" -n ' +{ + "format_version": 2, + "header": { + "description": "Adds 3D items for use with a Geyser proxy", + "name": $pack_desc, + "uuid": ($uuid3 | ascii_downcase), + "version": [1, 0, 0], + "min_engine_version": [ 1, 16, 100] + }, + "modules": [ + { + "description": "Adds 3D items for use with a Geyser proxy", + "type": "data", + "uuid": ($uuid4 | ascii_downcase), + "version": [1, 0, 0] + } + ], + "dependencies": [ + { + "uuid": ($uuid1 | ascii_downcase), + "version": [1, 0, 0] + } + ] +} +' | sponge ./target/bp/manifest.json + +# generate rp terrain_texture.json +status_message process "Generating resource pack terrain texture definition" +jq -nc ' +{ + "resource_pack_name": "vanilla", + "texture_name": "atlas.terrain", + "padding": 8, + "num_mip_levels": 4, + "texture_data": { + "gmdl_atlas": { + "textures": "textures/blocks/geysercmd/gmdl_atlas" + } + } +} +' | sponge ./target/rp/textures/terrain_texture.json + +status_message process "Generating resource pack disabling animation" +# generate our disabling animation +jq -nc ' +{ + "format_version": "1.8.0", + "animations": { + "animation.geysercmd.disable": { + "loop": true, + "override_previous_animation": true, + "bones": { + "geysercmd": { + "scale": 0 + } + } + } + } +} +' | sponge ./target/rp/animations/geysercmd/animation.geysercmd.disable.json +status_message completion "Initial pack setup complete\n" + +jq -r '.[] | select(.parent != null) | [.path, .geyserID, .parent] | @tsv | gsub("\\t";",")' config.json | sponge pa.csv + +_start=1 +_end="$(jq -r '(. | length) + ([.[] | select(.parent != null)] | length)' config.json)" +cur_pos=0 + +function ProgressBar { + let _progress=(${1}*100/${2}*100)/100 + let _done=(${_progress}*6)/10 + let _left=60-$_done + _fill=$(printf "%${_done}s") + _empty=$(printf "%${_left}s") +printf "\r\e[37m█\e[m \e[37m${_fill// /█}\e[m\e[37m${_empty// /•}\e[m \e[37m█\e[m \e[33m${_progress}%\e[m\n" +} + +# first, deal with parented models +while IFS=, read -r file gid parental +do + cur_pos=$((cur_pos+1)) + elements="$(jq -rc '.elements' ${file} | tee elements.temp)" + element_parent=${file} + textures="$(jq -rc '.textures' ${file} | tee textures.temp)" + display="$(jq -rc '.display' ${file} | tee display.temp)" + status_message process "Locating parental info for child model with GeyserID ${gid}" + + # itterate through parented models until they all have geometry, display, and textures + until [[ ${elements} != null && ${textures} != null && ${display} != null ]] || [[ ${parental} = null ]] + do + if [[ ${elements} = null ]] + then + elements="$(jq -rc '.elements' ${parental} 2> /dev/null | tee elements.temp || echo && echo null)" + element_parent=${parental} + fi + if [[ ${textures} = null ]] + then + textures="$(jq -rc '.textures' ${parental} 2> /dev/null | tee textures.temp || echo && echo null)" + fi + if [[ ${display} = null ]] + then + display="$(jq -rc '.display' ${parental} 2> /dev/null | tee display.temp || echo && echo null)" + fi + parental="$(jq -rc 'def namespace: if contains(":") then sub("\\:(.+)"; "") else "minecraft" end; ("./assets/" + (.parent | namespace) + "/models/" + ((.parent? // empty) | sub("(.*?)\\:"; "")) + ".json") // "null"' ${parental} 2> /dev/null || echo && echo null)" + done + + # if we can, generate a model now + if [[ ${elements} != null && ${textures} != null ]] + then + jq -n --slurpfile jelements elements.temp --slurpfile jtextures textures.temp --slurpfile jdisplay display.temp ' + { + "textures": ($jtextures[]), + "elements": ($jelements[]) + } + (if $jdisplay then ({"display": ($jdisplay[])}) else {} end) + ' | sponge ${file} + status_message completion "Located all parental info for Child ${gid}" + ProgressBar ${cur_pos} ${_end} + echo + # otherwise, remove it from our config + else + status_message plain "Suitable parent information was not availbile for ${gid}..." + status_message critical "Deleting ${gid} from config" + ProgressBar ${cur_pos} ${_end} + echo + jq --arg gid "${gid}" 'del(.[$gid])' config.json | sponge config.json + fi + +done < pa.csv + +# make sure we crop all mcmeta associated png files +status_message process "Cropping animated textures" +for i in $(find ./assets/**/textures -type f -name "*.mcmeta" | sed 's/\.mcmeta//'); do magick ${i} -background none -gravity North -extent "%[fx:h /dev/null 2>&1 +status_message completion "Sprite sheet successfully generated" +mv spritesheet.png ./target/rp/textures/blocks/geysercmd/gmdl_atlas.png + +# begin conversion +jq -r '.[] | [.path, .geyserID] | @tsv | gsub("\\t";",")' config.json | sponge all.csv + +while IFS=, read -r file gid +do + convert_model () { + local file=${1} + local gid=${2} + + status_message process "Starting conversion of model with GeyserID ${gid}" + + jq --slurpfile atlas spritesheet.json --arg binding "c.item_slot == 'head' ? 'head' : q.item_slot_to_bone_name(c.item_slot)" --arg model_name "${gid}" -c ' + .textures as $texture_list | + + def namespace: if contains(":") then sub("\\:(.+)"; "") else "minecraft" end; + + def totexture($input): ($texture_list[($input[1:])]? // ([$texture_list[]][0])); + + def topath($input): ("./assets/" + ($input | namespace) + "/textures/" + ($input | sub("(.*?)\\:"; "")) + ".png"); + + def texturedata($input): $atlas[] | .frames | (.[topath(totexture($input))] // ."./assets/minecraft/textures/0.png"); + + def roundit: (.*10000 | round) / 10000; + + def element_array: + .elements | map({ + "origin": [((-.to[0] + 8) | roundit), ((.from[1]) | roundit), ((.from[2] - 8) | roundit)], + "size": [((.to[0] - .from[0]) | roundit), ((.to[1] - .from[1]) | roundit), ((.to[2] - .from[2]) | roundit)], + "rotation": (if (.rotation.axis) == "x" then [(.rotation.angle | tonumber * -1), 0, 0] elif (.rotation.axis) == "y" then [0, (.rotation.angle | tonumber * -1), 0] elif (.rotation.axis) == "z" then [0, 0, (.rotation.angle | tonumber)] else null end), + "pivot": (if .rotation.origin then [((- .rotation.origin[0] + 8) | roundit), (.rotation.origin[1] | roundit), ((.rotation.origin[2] - 8) | roundit)] else null end), + "uv": ( + def uv_calc($input): + (if (.faces | .[$input]) then + (.faces | .[$input].texture) as $input_n + | ( (((((.faces | .[$input].uv[0]) * (texturedata($input_n) | .frame.w) * 0.0625) + (texturedata($input_n) | .frame.x)) * (16 / ($atlas[] | .meta.size.w))) ) ) as $fn0 + | ( (((((.faces | .[$input].uv[1]) * (texturedata($input_n) | .frame.h) * 0.0625) + (texturedata($input_n) | .frame.y)) * (16 / ($atlas[] | .meta.size.h))) ) ) as $fn1 + | ( (((((.faces | .[$input].uv[2]) * (texturedata($input_n) | .frame.w) * 0.0625) + (texturedata($input_n) | .frame.x)) * (16 / ($atlas[] | .meta.size.w))) ) ) as $fn2 + | ( (((((.faces | .[$input].uv[3]) * (texturedata($input_n) | .frame.h) * 0.0625) + (texturedata($input_n) | .frame.y)) * (16 / ($atlas[] | .meta.size.h))) ) ) as $fn3 + | (($fn2 - $fn0) as $num | [([-1, $num] | max), 1] | min) as $x_sign + | (($fn3 - $fn1) as $num | [([-1, $num] | max), 1] | min) as $y_sign | + { + "uv": [(($fn0 + (0.016 * $x_sign)) | roundit), (($fn1 + (0.016 * $y_sign)) | roundit)], + "uv_size": [((($fn2 - $fn0) - (0.016 * $x_sign)) | roundit), ((($fn3 - $fn1) - (0.016 * $y_sign)) | roundit)] + } else null end); + { + "north": uv_calc("north"), + "south": uv_calc("south"), + "east": uv_calc("east"), + "west": uv_calc("west"), + "up": uv_calc("up"), + "down": uv_calc("down") + }) + }) | walk( if type == "object" then with_entries(select(.value != null)) else . end) + ; + + def pivot_groups: + (element_array) as $element_array | + [[.elements[].rotation] | unique | .[] | select (.!=null)] + | map(( + [((- .origin[0] + 8) | roundit), (.origin[1] | roundit), ((.origin[2] - 8) | roundit)] as $i_piv | + (if (.axis) == "x" then [(.angle | tonumber * -1), 0, 0] elif (.axis) == "y" then [0, (.angle | tonumber * -1), 0] else [0, 0, (.angle | tonumber)] end) as $i_rot | + { + "parent": "geysercmd_z", + "pivot": ($i_piv), + "rotation": ($i_rot), + "mirror": true, + "cubes": [($element_array | .[] | select(.rotation == $i_rot and .pivot == $i_piv))] + })) + ; + + { + "format_version": "1.16.0", + "minecraft:geometry": [{ + "description": { + "identifier": ("geometry.geysercmd." + ($model_name)), + "texture_width": 16, + "texture_height": 16, + "visible_bounds_width": 4, + "visible_bounds_height": 4.5, + "visible_bounds_offset": [0, 0.75, 0] + }, + "bones": ([{ + "name": "geysercmd", + "binding": $binding, + "pivot": [0, 8, 0] + }, { + "name": "geysercmd_x", + "parent": "geysercmd", + "pivot": [0, 8, 0] + }, { + "name": "geysercmd_y", + "parent": "geysercmd_x", + "pivot": [0, 8, 0] + }, { + "name": "geysercmd_z", + "parent": "geysercmd_y", + "pivot": [0, 8, 0], + "cubes": [(element_array | .[] | select(.rotation == null))] + }] + (pivot_groups | map(del(.cubes[].rotation)) | to_entries | map( (.value.name = "rot_\(1+.key)" ) | .value))) + }] + } + + ' ${file} | sponge ./target/rp/models/blocks/geysercmd/${gid}.json + + # generate our rp animations via display settings + jq -c --arg model_name "${gid}" ' + + { + "format_version": "1.8.0", + "animations": { + ("animation.geysercmd." + ($model_name) + ".thirdperson_main_hand"): { + "loop": true, + "bones": { + "geysercmd_x": (if .display.thirdperson_righthand then { + "rotation": (if .display.thirdperson_righthand.rotation then [(- .display.thirdperson_righthand.rotation[0]), 0, 0] else null end), + "position": (if .display.thirdperson_righthand.translation then [(- .display.thirdperson_righthand.translation[0]), (.display.thirdperson_righthand.translation[1]), (.display.thirdperson_righthand.translation[2])] else null end), + "scale": (if .display.thirdperson_righthand.scale then [(.display.thirdperson_righthand.scale[0]), (.display.thirdperson_righthand.scale[1]), (.display.thirdperson_righthand.scale[2])] else null end) + } else null end), + "geysercmd_y": (if .display.thirdperson_righthand.rotation then { + "rotation": (if .display.thirdperson_righthand.rotation then [0, (- .display.thirdperson_righthand.rotation[1]), 0] else null end) + } else null end), + "geysercmd_z": (if .display.thirdperson_righthand.rotation then { + "rotation": [0, 0, (.display.thirdperson_righthand.rotation[2])] + } else null end), + "geysercmd": { + "rotation": [90, 0, 0], + "position": [0, 13, -3] + } + } + }, + ("animation.geysercmd." + ($model_name) + ".thirdperson_off_hand"): { + "loop": true, + "bones": { + "geysercmd_x": (if .display.thirdperson_lefthand then { + "rotation": (if .display.thirdperson_lefthand.rotation then [(- .display.thirdperson_lefthand.rotation[0]), 0, 0] else null end), + "position": (if .display.thirdperson_lefthand.translation then [(- .display.thirdperson_lefthand.translation[0]), (.display.thirdperson_lefthand.translation[1]), (.display.thirdperson_lefthand.translation[2])] else null end), + "scale": (if .display.thirdperson_lefthand.scale then [(.display.thirdperson_lefthand.scale[0]), (.display.thirdperson_lefthand.scale[1]), (.display.thirdperson_lefthand.scale[2])] else null end) + } else null end), + "geysercmd_y": (if .display.thirdperson_lefthand.rotation then { + "rotation": (if .display.thirdperson_lefthand.rotation then [0, (- .display.thirdperson_lefthand.rotation[1]), 0] else null end) + } else null end), + "geysercmd_z": (if .display.thirdperson_lefthand.rotation then { + "rotation": [0, 0, (.display.thirdperson_lefthand.rotation[2])] + } else null end), + "geysercmd": { + "rotation": [90, 0, 0], + "position": [0, 13, -3] + } + } + }, + ("animation.geysercmd." + ($model_name) + ".head"): { + "loop": true, + "bones": { + "geysercmd_x": { + "rotation": (if .display.head.rotation then [(- .display.head.rotation[0]), 0, 0] else null end), + "position": (if .display.head.translation then [(- .display.head.translation[0] * 0.625), (.display.head.translation[1] * 0.625), (.display.head.translation[2] * 0.625)] else null end), + "scale": (if .display.head.scale then (.display.head.scale | map(. * 0.625)) else 0.625 end) + }, + "geysercmd_y": (if .display.head.rotation then { + "rotation": [0, (- .display.head.rotation[1]), 0] + } else null end), + "geysercmd_z": (if .display.head.rotation then { + "rotation": [0, 0, (.display.head.rotation[2])] + } else null end), + "geysercmd": { + "position": [0, 19.5, 0] + } + } + }, + ("animation.geysercmd." + ($model_name) + ".firstperson_main_hand"): { + "loop": true, + "bones": { + "geysercmd": { + "rotation": [90, 60, -40], + "position": [4, 10, 4], + "scale": 1.5 + }, + "geysercmd_x": { + "position": (if .display.firstperson_righthand.translation then [(- .display.firstperson_righthand.translation[0]), (.display.firstperson_righthand.translation[1]), (- .display.firstperson_righthand.translation[2])] else null end), + "rotation": (if .display.firstperson_righthand.rotation then [(- .display.firstperson_righthand.rotation[0]), 0, 0] else [0.1, 0.1, 0.1] end), + "scale": (if .display.firstperson_righthand.scale then (.display.firstperson_righthand.scale) else null end) + }, + "geysercmd_y": (if .display.firstperson_righthand.rotation then { + "rotation": [0, (- .display.firstperson_righthand.rotation[1]), 0] + } else null end), + "geysercmd_z": (if .display.firstperson_righthand.rotation then { + "rotation": [0, 0, (.display.firstperson_righthand.rotation[2])] + } else null end) + } + }, + ("animation.geysercmd." + ($model_name) + ".firstperson_off_hand"): { + "loop": true, + "bones": { + "geysercmd": { + "rotation": [90, 60, -40], + "position": [4, 10, 4], + "scale": 1.5 + }, + "geysercmd_x": { + "position": (if .display.firstperson_lefthand.translation then [(.display.firstperson_lefthand.translation[0]), (.display.firstperson_lefthand.translation[1]), (- .display.firstperson_lefthand.translation[2])] else null end), + "rotation": (if .display.firstperson_lefthand.rotation then [(- .display.firstperson_lefthand.rotation[0]), 0, 0] else [0.1, 0.1, 0.1] end), + "scale": (if .display.firstperson_lefthand.scale then (.display.firstperson_lefthand.scale) else null end) + }, + "geysercmd_y": (if .display.firstperson_lefthand.rotation then { + "rotation": [0, (- .display.firstperson_lefthand.rotation[1]), 0] + } else null end), + "geysercmd_z": (if .display.firstperson_lefthand.rotation then { + "rotation": [0, 0, (.display.firstperson_lefthand.rotation[2])] + } else null end) + } + } + } + } | walk( if type == "object" then with_entries(select(.value != null)) else . end) + + ' ${file} | sponge ./target/rp/animations/geysercmd/animation.${gid}.json + + # generate our rp attachable definition + jq -c -n --arg attachable_material "${attachable_material}" --arg v_main "v.main_hand = c.item_slot == 'main_hand';" --arg v_off "v.off_hand = c.item_slot == 'off_hand';" --arg v_head "v.head = c.item_slot == 'head';" --arg model_name "${gid}" --arg texture_name "${texture_id}" ' + + { + "format_version": "1.10.0", + "minecraft:attachable": { + "description": { + "identifier": ("geysercmd:" + $model_name), + "materials": { + "default": $attachable_material, + "enchanted": $attachable_material + }, + "textures": { + "default": "textures/blocks/geysercmd/gmdl_atlas", + "enchanted": "textures/misc/enchanted_item_glint" + }, + "geometry": { + "default": ("geometry.geysercmd." + $model_name) + }, + "scripts": { + "pre_animation": [$v_main, $v_off, $v_head], + "animate": [ + {"thirdperson_main_hand": "v.main_hand && !c.is_first_person"}, + {"thirdperson_off_hand": "v.off_hand && !c.is_first_person"}, + {"thirdperson_head": "v.head && !c.is_first_person"}, + {"firstperson_main_hand": "v.main_hand && c.is_first_person"}, + {"firstperson_off_hand": "v.off_hand && c.is_first_person"}, + {"firstperson_head": "c.is_first_person && v.head"} + ] + }, + "animations": { + "thirdperson_main_hand": ("animation.geysercmd." + $model_name + ".thirdperson_main_hand"), + "thirdperson_off_hand": ("animation.geysercmd." + $model_name + ".thirdperson_off_hand"), + "thirdperson_head": ("animation.geysercmd." + $model_name + ".head"), + "firstperson_main_hand": ("animation.geysercmd." + $model_name + ".firstperson_main_hand"), + "firstperson_off_hand": ("animation.geysercmd." + $model_name + ".firstperson_off_hand"), + "firstperson_head": "animation.geysercmd.disable" + }, + "render_controllers": [ "controller.render.item_default" ] + } + } + } + + ' | sponge ./target/rp/attachables/geysercmd/${gid}.attachable.json + + # generate our bp block definition + jq -c -n --arg block_material "${block_material}" --arg geyser_id "${gid}" ' + + { + "format_version": "1.16.200", + "minecraft:block": { + "description": { + "identifier": ("geysercmd:" + $geyser_id) + }, + "components": { + "minecraft:material_instances": { + "*": { + "texture": "gmdl_atlas", + "render_method": $block_material, + "face_dimming": false, + "ambient_occlusion": false + } + }, + "tag:geysercmd:example_block": {}, + "minecraft:geometry": ("geometry.geysercmd." + $geyser_id), + "minecraft:placement_filter": { + "conditions": [ + { + "allowed_faces": [ + ], + "block_filter": [ + ] + } + ] + } + } + } + } + + ' | sponge ./target/bp/blocks/geysercmd/${gid}.json + local tot_pos=$((cur_pos + $(ls ./target/bp/blocks/geysercmd/*.json | wc -l))) + status_message completion "${gid} converted\n$(ProgressBar ${tot_pos} ${_end})" + echo + } + convert_model ${file} ${gid} & + +done < all.csv +wait # wait for all the jobs to finish + +# write lang file US +status_message process "Writing en_US and en_GB lang files" +mkdir ./target/rp/texts +jq -r ' + +def format: (.[0:1] | ascii_upcase ) + (.[1:] | gsub( "_(?[a-z])"; (" " + .a) | ascii_upcase)); +to_entries[]|"\("tile.geysercmd:" + .key + ".name")=\(.value.item | format)" + +' config.json | sponge ./target/rp/texts/en_US.lang + +# copy US lang to GB +cp ./target/rp/texts/en_US.lang ./target/rp/texts/en_GB.lang + +# write supported languages file +jq -n '["en_US","en_GB"]' | sponge ./target/rp/texts/languages.json +status_message completion "en_US and en_GB lang files written\n" + +# apply image compression if we can +#if command -v pngquant >/dev/null 2>&1 ; then +# status_message completion "Optional dependency pngquant detected" +# status_message process "Attempting image compression" +# pngquant -f --skip-if-larger --ext .png --strip ./target/rp/textures/blocks/geysercmd/*.png +# status_message completion "Image compression complete" +# echo +#fi + +# attempt to merge with existing pack if input was provided +if test -f ${merge_input}; then + mkdir inputbedrockpack + status_message process "Decompressing input bedrock pack" + unzip -q ${merge_input} -d ./inputbedrockpack + status_message process "Merging input bedrock pack with generated bedrock assets" + cp -n -r "./inputbedrockpack"/* './target/rp/' + if test -f ./inputbedrockpack/textures/terrain_texture.json; then + status_message process "Merging terrain texture files" + jq -s ' + {"resource_pack_name": "vanilla", + "texture_name": "atlas.terrain", + "padding": 8, "num_mip_levels": 4, + "texture_data": (.[1].texture_data + .[0].texture_data)} + ' ./target/rp/textures/terrain_texture.json ./inputbedrockpack/textures/terrain_texture.json | sponge ./target/rp/textures/terrain_texture.json + fi + if test -f ./inputbedrockpack/texts/languages.json; then + status_message process "Merging languages file" + jq -s '.[0] + .[1] | unique' | sponge ./target/rp/texts/languages.json + fi + if test -f ./inputbedrockpack/texts/en_US.lang; then + status_message process "Merging en_US lang file" + cat ./inputbedrockpack/texts/en_US.lang >> ./target/rp/texts/en_US.lang + fi + if test -f ./inputbedrockpack/texts/en_GB.lang; then + status_message process "Merging en_GB lang file" + cat ./inputbedrockpack/texts/en_GB.lang >> ./target/rp/texts/en_GB.lang + fi + status_message critical "Deleting input bedrock pack scratch direcotry" + rm -rf inputbedrockpack + status_message completion "Input bedrock pack merged with generated assets\n" +fi + +# cleanup +status_message critical "Deleting scratch files" +rm -rf assets && rm -f pack.mcmeta && rm -f pack.png && rm -f parents.json && rm -f all.csv && rm -f pa.csv && rm -f README.md && rm -f README.txt && rm -f *.temp && rm -f spritesheet.json + +status_message critical "Deleting unused entries from config" +jq 'map_values(del(.path, .element_parent, .parent, .geyserID))' config.json | sponge config.json +status_message process "Moving config to target directory" +mv config.json ./target/config.json +echo + +status_message process "Compressing output packs" +mkdir ./target/packaged +cd ./target/rp > /dev/null && zip -rq8 geyser_rp.mcpack . -x "*/.*" && cd - > /dev/null && mv ./target/rp/geyser_rp.mcpack ./target/packaged/geyser_rp.mcpack +cd ./target/bp > /dev/null && zip -rq8 geyser_bp.mcpack . -x "*/.*" && cd - > /dev/null && mv ./target/bp/geyser_bp.mcpack ./target/packaged/geyser_bp.mcpack +cd ./target/packaged > /dev/null && zip -rq8 geyser.mcaddon . -i "*.mcpack" && cd - > /dev/null +mkdir ./target/unpackaged +mv ./target/rp ./target/unpackaged/rp && mv ./target/bp ./target/unpackaged/bp + +echo +printf "\e[32m[+]\e[m \e[1m\e[37mConversion Process Complete\e[m\n\n\e[37mExiting...\e[m\n\n"