├── remove-parrots ├── tests │ ├── server.properties │ ├── README.md │ ├── orig_playerdata │ │ └── 0b5a6ac9-2300-4e42-ad9f-e1650e807bc1.dat │ └── initialize_test.sh └── remove-parrots.sh ├── region-parser ├── .gitignore ├── MineOps.mca └── region-parser.sh ├── linux-laptop-touchpad-fix ├── enable_simultaneous_mouse_keyboard.sh └── disable_simultaneous_mouse_keyboard.sh ├── ungen-region-search └── ungen-region-search.sh ├── true-world-time └── extract_region_timestamps.py ├── LICENSE ├── README.md ├── generate-bedrock-mcworld-files └── packageMinecraftWorlds.sh └── inventory-search └── inventory-search.sh /remove-parrots/tests/server.properties: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /region-parser/.gitignore: -------------------------------------------------------------------------------- 1 | out/* 2 | mca2nbt 3 | MineOps 4 | timestamp-debug.sh 5 | *_nbt 6 | -------------------------------------------------------------------------------- /region-parser/MineOps.mca: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudofox/minecraft-utils/HEAD/region-parser/MineOps.mca -------------------------------------------------------------------------------- /remove-parrots/tests/README.md: -------------------------------------------------------------------------------- 1 | Reset test with ./initialize_test.sh 2 | 3 | Test with ../remote-parrots.sh Morgan_Ladimore 4 | 5 | -------------------------------------------------------------------------------- /remove-parrots/tests/orig_playerdata/0b5a6ac9-2300-4e42-ad9f-e1650e807bc1.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudofox/minecraft-utils/HEAD/remove-parrots/tests/orig_playerdata/0b5a6ac9-2300-4e42-ad9f-e1650e807bc1.dat -------------------------------------------------------------------------------- /remove-parrots/tests/initialize_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Puts the playerdata containing parrots back into the world, wipes out backups 4 | 5 | rm -f world/playerdata/*backup 6 | cat orig_playerdata/0b5a6ac9-2300-4e42-ad9f-e1650e807bc1.dat > world/playerdata/0b5a6ac9-2300-4e42-ad9f-e1650e807bc1.dat 7 | 8 | echo "Successfully reset playerdata, ready to test with ../remove-parrots.sh Morgan_Ladimore" 9 | -------------------------------------------------------------------------------- /linux-laptop-touchpad-fix/enable_simultaneous_mouse_keyboard.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | XINPUT_DEV=$(xinput | grep -Po "SynPS/2 Synaptics TouchPad.*id=\K.+?(?=\t)") 4 | 5 | OPTION_ID=$(xinput --list-props $XINPUT_DEV | grep -Po "libinput Disable\ While\ Typing\ Enabled\ \(\K.+?(?=\))") 6 | 7 | xinput --set-prop $XINPUT_DEV $OPTION_ID 0 8 | 9 | echo "Updated Option ID $OPTION_ID of device $XINPUT_DEV to 0 (false). You can now move the mouse and type." 10 | -------------------------------------------------------------------------------- /linux-laptop-touchpad-fix/disable_simultaneous_mouse_keyboard.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | XINPUT_DEV=$(xinput | grep -Po "SynPS/2 Synaptics TouchPad.*id=\K.+?(?=\t)") 4 | 5 | OPTION_ID=$(xinput --list-props $XINPUT_DEV | grep -Po "libinput Disable\ While\ Typing\ Enabled\ \(\K.+?(?=\))") 6 | 7 | xinput --set-prop $XINPUT_DEV $OPTION_ID 1 8 | 9 | echo "Updated Option ID $OPTION_ID of device $XINPUT_DEV to 1 (true). You can no longer move the mouse and type at the same time" 10 | -------------------------------------------------------------------------------- /ungen-region-search/ungen-region-search.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # List ungenerated Minecraft region files ranging from r.-100.-100.mca to r.100.100.mca 4 | 5 | # five parameters: regionDir startX, startZ, endX, endZ 6 | if [ $# -ne 5 ]; then 7 | echo "List ungenerated Minecraft region files within a specified range" 8 | echo "Usage: $0 regionDir startX startZ endX endZ" 9 | exit 1 10 | fi 11 | 12 | # Verify regionDir exists 13 | if [ ! -d $1 ]; then 14 | echo "regionDir $1 does not exist" 15 | exit 1 16 | fi 17 | 18 | for x_region in $(seq $2 $3); do 19 | for z_region in $(seq $4 $5); do 20 | region_file="$1/r.$x_region.$z_region.mca" 21 | if [ ! -f "$region_file" ]; then 22 | echo "Region $x_region $z_region has not yet been generated" 23 | fi 24 | done 25 | done 26 | -------------------------------------------------------------------------------- /true-world-time/extract_region_timestamps.py: -------------------------------------------------------------------------------- 1 | # 2 | # @author sudofox 3 | # 4 | # Extract and print the chunk timestamps from a minecraft region file 5 | # 6 | 7 | import sys 8 | 9 | # usage: ./region_timestamps.py 10 | 11 | if len(sys.argv) == 1: 12 | print("usage: ./extract_region_timestamps.py ") 13 | sys.exit(1) 14 | 15 | # open the region file 16 | region_file = open(sys.argv[1], "rb") 17 | 18 | # read the header (8 kB) 19 | 20 | # header layout is as follows: 21 | # byte 0-4095: locations (1024 entries) 22 | # byte 4096-8191: timestamps (1024 entries) 23 | # byte 8192: data (chunks and unused space) 24 | 25 | region_file.seek(0) 26 | header = region_file.read(8192) 27 | 28 | # read all the chunk timestamps 29 | timestamps = [] 30 | for i in range(1024): 31 | timestamp = header[4096 + i*4:4096 + (i+1)*4] 32 | timestamp = int.from_bytes(timestamp, byteorder="big") 33 | timestamps.append(timestamp) 34 | 35 | # print the timestamps separated by newlines 36 | for timestamp in timestamps: 37 | print(timestamp) 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Austin Burk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sudofox/minecraft-utils 2 | 3 | Various tools and scripts that I produce while working on Minecraft-related things. 4 | 5 | ## remove-parrots 6 | Tool for removing parrots from playerdata, made to solve an annoyance on a particular server 7 | 8 | Must be run from root of Minecraft server folder. 9 | 10 | Usage: `./remove-parrots.sh MinecraftUsername` 11 | 12 | 13 | ## inventory-search 14 | Tool for scanning all playerdata files for items within the inventory or Ender Chest. 15 | 16 | Invoke it when your current working directory is the root of the Minecraft server folder. Can be used with some regex. 17 | 18 | Usage: `./inventory-search.sh cobblestone` 19 | `./inventory-search.sh '[a-z]{1,}_shulker_box'` 20 | 21 | 22 | ## region-parser 23 | 24 | Tool that parses a Minecraft region file (.mca) and extracts each chunk to an NBT file. 25 | 26 | I plan on making this into some sort of region/chunk search tool to find things like lost tools and items. 27 | 28 | Usage: `./region-parser.sh regionfile.mca` 29 | 30 | 31 | ## linux-laptop-touchpad-fix 32 | 33 | Two scripts that enable/disable the blocking of touchpad input when a key is pressed. Might need to be tweaked for your specific laptop. 34 | 35 | ## true-world-time 36 | 37 | For any given region file, get the lowest timestamp on a chunk. Run this on all of your region files and then sort the output to find the oldest chunk in your world. 38 | 39 | The script only does one at a time, so you'll need to run it on each region file. 40 | -------------------------------------------------------------------------------- /generate-bedrock-mcworld-files/packageMinecraftWorlds.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # by sudofox, with much love to Ari 3 | # to create .mcworld files from all the folders in the cwd (when executed, you should be sitting inside the minecraftWorlds dir) 4 | # these can be restored by minecraft bedrock on android 5 | 6 | # store the directory containing the directories to be zipped 7 | SRC_DIR=$(pwd) 8 | 9 | # store the directory where the zip files should be created 10 | DEST_DIR=~/Documents/MCBackup/mcworlds 11 | 12 | # create the destination directory if it doesn't exist 13 | if [ ! -d "$DEST_DIR" ]; then 14 | mkdir -p "$DEST_DIR" 15 | fi 16 | 17 | # loop through the directories in the source directory 18 | for d in "$SRC_DIR"/*/ ; do 19 | # extract the directory name from the full path 20 | dir_name=$(basename "$d") 21 | 22 | # navigate to the directory 23 | cd "$d" 24 | 25 | # create a zip file of the current directory with the same name 26 | zip -r "$DEST_DIR/${dir_name}.mcworld" . 27 | 28 | # navigate back to the original directory 29 | cd "$SRC_DIR" 30 | 31 | # push the zip file to the Android device 32 | adb push "$DEST_DIR/${dir_name}.mcworld" /sdcard/mcworld_import/ 33 | 34 | # wait for the user to confirm before opening the next world 35 | read -p "Press enter to open the next world in Minecraft" 36 | done 37 | 38 | # loop through the .mcworld files in the mcworld_import directory 39 | for f in $(adb shell "ls /sdcard/mcworld_import/*.mcworld"); do 40 | # open the .mcworld file in Minecraft 41 | adb shell am start -a android.intent.action.VIEW -d "file://$f" -t application/vnd.minecraft-world 42 | 43 | read -p "Press enter to open the next world in Minecraft" 44 | done 45 | -------------------------------------------------------------------------------- /inventory-search/inventory-search.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # @author sudofox 4 | # 5 | # Inventory Searcher - Searches for items in a player's inventory or ender chest. 6 | # 7 | 8 | COLOR_BOLD=$(tput bold) 9 | COLOR_RESET=$(tput sgr0) 10 | COLOR_DIM=$(tput dim) 11 | COLOR_RED=$(tput setaf 1) 12 | COLOR_GREEN=$(tput setaf 2) 13 | COLOR_BLUE=$(tput setaf 4) 14 | 15 | INFO_HEADER=$(echo -n "${COLOR_RESET}${COLOR_BOLD}"'[INFO] '$COLOR_RESET) 16 | ERROR_HEADER=$(echo -n "${COLOR_RESET}${COLOR_BOLD}${COLOR_RED}"'[ERROR] '"$COLOR_RESET") 17 | 18 | usage() { 19 | echo "${COLOR_BOLD}Usage:${COLOR_GREEN}${COLOR_DIM}" $0 "minecraft_item_id${COLOR_RESET}" 20 | echo "${COLOR_BOLD}Example: ${COLOR_GREEN}${COLOR_DIM}"$0" red_shulker_box${COLOR_RESET}" 21 | echo "${COLOR_BOLD}Takes regex: ${COLOR_GREEN}${COLOR_DIM}"$0" '[a-z]{1,}_shulker_box'${COLOR_RESET}" 22 | exit 23 | } 24 | 25 | echo "${COLOR_BLUE}${COLOR_BOLD}Inventory/Ender Chest search tool by Sudofox${COLOR_RESET}" 26 | 27 | [ -z "$1" ] && usage 28 | 29 | if ! command -v nbted >/dev/null; then 30 | echo $ERROR_HEADER'Could not find nbted (cargo install nbted)' 31 | exit 1 32 | fi 33 | if ! command -v jq >/dev/null; then 34 | echo $ERROR_HEADER'Could not find jq (for json parsing), please install it first' 35 | exit 1 36 | fi 37 | 38 | # check that we are in the root of the minecraft server 39 | if [ ! -f ./server.properties ]; then 40 | echo $ERROR_HEADER'Could not find server.properties; we are not in the root directory for the Minecraft server.' 41 | exit 1 42 | fi 43 | 44 | # When we can't pull the username from lastKnownName... 45 | # usage: username_for_uuid 46 | username_for_uuid() { 47 | UUID=$(echo -n $1 | tr -d '-') 48 | echo -n $(curl -s https://api.mojang.com/user/profiles/$UUID/names | jq -r '.[0].name') 49 | 50 | } 51 | 52 | for i in $(ls world*/playerdata/*.dat); do 53 | UUID=$(echo -n $i | grep -Po "playerdata/\K.+?(?=\.)") 54 | NAME=$(nbted -p $i | grep -Po "lastKnownName\"\ \"\K.+?(?=\")") 55 | if ! [[ $NAME =~ [a-zA-Z0-9_] ]]; then 56 | NAME=$(username_for_uuid $UUID) 57 | fi 58 | 59 | CHECK=$(nbted -p $i | grep -Po "String.*\Kminecraft:$1" | awk '{print $0}') 60 | 61 | if ! [[ $(echo "$CHECK" | wc -w) -eq 0 ]]; then 62 | NAME=$(nbted -p $i | grep -Po "lastKnownName\"\ \"\K.+?(?=\")") 63 | if ! [[ $NAME =~ [a-zA-Z0-9_] ]]; then 64 | NAME=$(username_for_uuid $UUID) 65 | fi 66 | echo "$CHECK" | awk -v name="$NAME" '{print name " - " $0}' 67 | fi 68 | 69 | done 70 | -------------------------------------------------------------------------------- /region-parser/region-parser.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # @author: sudofox 4 | # 5 | # This is a script that uses various CLI utilities and bash to parse a Minecraft 6 | # region file and extract the chunks to a directory. It's not really meant for 7 | # daily use, and was written more as a way for me to practice with bash. :D 8 | # 9 | # if you want debug output, toggle DEBUG to true 10 | # there's also some things you can uncomment to get more debug output 11 | # 12 | 13 | echo "+=============================+" 14 | echo "| region-parser.sh by Sudofox |" 15 | echo "+=============================+" 16 | 17 | # Some values related to the format 18 | # There are 1,024 chunks in each region file, occupying the first 4096 bytes of the file 19 | 20 | # xxd -r -p to get raw binary back from a string like 01020304 21 | # xxd -p -c 8 to get 8 bytes as an unbroken string of hex 22 | 23 | DEBUG=false 24 | 25 | log_debug() { 26 | if $DEBUG; then 27 | tput dim 28 | echo "$@" 29 | tput sgr0 30 | fi 31 | } 32 | 33 | usage() { 34 | echo "Usage: $0 .mca" 35 | exit 1 36 | } 37 | 38 | # Validation 39 | 40 | if [ $# -eq 0 ]; then usage; fi 41 | 42 | # File exists? 43 | if [ ! -f $1 ]; then 44 | echo "[ERROR] Couldn't open chunk file $1" 45 | usage 46 | fi 47 | 48 | # xxd installed? 49 | if ! command -v xxd >/dev/null; then 50 | echo '[ERROR] Could not find xxd, please install first.' 51 | exit 1 52 | fi 53 | 54 | filename=$(echo -n $1 | sed 's/\.nbt//g') 55 | 56 | if [ ! -d ${filename}_nbt ]; then 57 | mkdir ${filename}_nbt 58 | else 59 | echo "[INFO] Deleting all NBT files in ./${filename}_nbt" 60 | rm -f ${filename}_nbt/*.nbt 61 | fi 62 | 63 | chunk_number=-1 64 | 65 | for location in $(head -c4096 $1 | xxd -p -c 4); do 66 | 67 | chunk_number=$((chunk_number + 1)) 68 | offset=$((0x${location:0:6})) 69 | if [[ $offset == 0 ]]; then 70 | continue # No chunk 71 | fi 72 | 73 | sectors=$((0x${location:6:2})) 74 | 75 | log_debug "[DEBUG] Chunk number: $chunk_number" 76 | log_debug "[DEBUG] Raw chunk bytes (hex): $location (offset = $offset (${location:0:6}) || sectors = $sectors)" 77 | 78 | timestamp=$((0x$(tail -c+$((($offset * 4) + 4097)) $1 | head -c4 | xxd -p -c 4))) 79 | 80 | # obtain the length and compression type 81 | 82 | chunkheader=$(tail -c+$(($offset * 4096 + 1)) $1 | head -c5 | xxd -p -c 5) 83 | log_debug "[DEBUG] chunk $chunk_number header: "$(echo $chunkheader | xxd -r -p | xxd -c 8) 84 | log_debug "[DEBUG] compression type: $((0x${chunkheader:8:2})) (01=gzip, 02=zlib)" 85 | 86 | chunk_length=$(((16777216 * 0x${chunkheader:0:2}) + (65536 * 0x${chunkheader:2:2}) + (256 * 0x${chunkheader:4:2}) + 0x${chunkheader:6:2})) 87 | log_debug "[DEBUG] Chunk data (start position, chunk_length): "$(($offset * 4096))", $chunk_length" 88 | 89 | # Decode chunk header 90 | if [[ $chunk_length -gt 0 ]]; then # skip zero-length chunks 91 | # log_debug "[DEBUG] chunk timestamp raw (hex) = $(tail -c+$(($offset+4096)) $1|head -c4|xxd -p -c 4)" 92 | # log_debug "[DEBUG] chunk timestamp = $timestamp = $(date -d @$timestamp)" 93 | echo "[INFO] Extracting chunk $chunk_number (modify time: $(date -d @$timestamp)) to ${filename}_nbt/$chunk_number.nbt" 94 | 95 | # We tack on the gzip magic at the beginning, but I believe it's missing something (e.g. crc) at the end of the file. As such, gzip normally errors out, so we ignore that. 96 | # still seems to be valid after decompression :) 97 | gzip -dcq <(printf "\x1f\x8b\x08\x00\x00\x00\x00\x00" | cat - <(tail -c+$((($offset * 4096) + 6)) $1 | head -c$(($chunk_length - 1)))) 2>/dev/null >${filename}_nbt/$chunk_number.nbt 98 | fi 99 | done 100 | -------------------------------------------------------------------------------- /remove-parrots/remove-parrots.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # @author sudofox 4 | # 5 | # Remove Parrot from playerdata file 6 | # written for a very specific use case, but could be useful as a reference for other things 7 | # 8 | # requires nbted - cargo install nbted 9 | # require jq - for proper parsing of username 10 | # Usage: /path/to/remove_parrot.sh MinecraftUsername 11 | # 12 | 13 | # Fetch color-codes 14 | COLOR_BOLD=$(tput bold) 15 | COLOR_RESET=$(tput sgr0) 16 | COLOR_DIM=$(tput dim) 17 | COLOR_RED=$(tput setaf 1) 18 | COLOR_GREEN=$(tput setaf 2) 19 | COLOR_BLUE=$(tput setaf 4) 20 | 21 | INFO_HEADER=$(echo -n "${COLOR_RESET}${COLOR_BOLD}"'[INFO] '$COLOR_RESET) 22 | ERROR_HEADER=$(echo -n "${COLOR_RESET}${COLOR_BOLD}${COLOR_RED}"'[ERROR] '"$COLOR_RESET") 23 | 24 | usage() { 25 | echo "${COLOR_BOLD}Usage:" $0 "MinecraftUsername${COLOR_RESET}" 26 | exit 27 | } 28 | 29 | echo "${COLOR_BLUE}${COLOR_BOLD}Parrot Removal Tool by Sudofox${COLOR_RESET}" 30 | 31 | [ -z "$1" ] && usage 32 | 33 | if ! command -v nbted >/dev/null; then 34 | echo $ERROR_HEADER'Could not find nbted (cargo install nbted)' 35 | exit 1 36 | fi 37 | if ! command -v jq >/dev/null; then 38 | echo $ERROR_HEADER'Could not find jq (for json parsing), please install it first' 39 | exit 1 40 | fi 41 | 42 | # Fetch UUID by username 43 | 44 | echo -n $INFO_HEADER'Fetching UUID for '"$1"'... ' 45 | UUID=$(curl -s https://api.mojang.com/users/profiles/minecraft/$1 | jq -r '.id') #|perl -pe 's/(\w{8})(\w{4})(\w{4})(\w{4})(\w{12})/\1-\2-\3-\4-\5/g') 46 | if [[ $(echo -n $UUID | wc -c) != 32 ]]; then 47 | echo "${COLOR_RESET}${COLOR_RED}"'Could not get UUID for username '"$1${COLOR_RESET}" 48 | exit 1 49 | fi 50 | 51 | FORMATTED_UUID=$(echo $UUID | perl -pe 's/(\w{8})(\w{4})(\w{4})(\w{4})(\w{12})/\1-\2-\3-\4-\5/g') 52 | 53 | echo $FORMATTED_UUID 54 | 55 | # check that we are in the root of the minecraft server 56 | if [ ! -f ./server.properties ]; then 57 | echo $ERROR_HEADER'Could not find server.properties; we are not in the root directory for the Minecraft server.' 58 | exit 1 59 | fi 60 | 61 | echo $INFO_HEADER'Checking worlds...' 62 | FOUND_PLAYERDATA=false 63 | for world in $(ls | grep world); do 64 | if [ -f $world/playerdata/$FORMATTED_UUID.dat ]; then 65 | FOUND_PLAYERDATA=true 66 | echo ${INFO_HEADER}${world}: Found $FORMATTED_UUID.dat 67 | # Just to make sure we actually have parrots 68 | 69 | CHECK_PARROTS=$(cat $world/playerdata/$FORMATTED_UUID.dat | gzip -dc | strings | egrep "ShoulderEntity(Left|Right)") 70 | if [[ $(echo -n "$CHECK_PARROTS" | wc -w) -eq 0 ]]; then 71 | echo ${INFO_HEADER}Found no parrots. 72 | continue 73 | else 74 | echo ${INFO_HEADER}Found $(echo -n "$CHECK_PARROTS" | wc -w) parrot\(s\). 75 | fi 76 | 77 | # Check if our NBT lines change before and after our bird removal 78 | 79 | backup_date=$(date +%m-%d-%Y_%H.%M.%S.%Z) 80 | cp -a $world/playerdata/$FORMATTED_UUID.dat{,.$backup_date.backup} 81 | echo ${INFO_HEADER}${world}: Backed up player data file to $world/playerdata/$FORMATTED_UUID.dat.$backup_date.backup 82 | 83 | NBT_LINES_PRE_AVICIDE=$(nbted -p $world/playerdata/$FORMATTED_UUID.dat | wc -l) 84 | REMOVE_PARROT=$(nbted -p $world/playerdata/$FORMATTED_UUID.dat | perl -p0e 's/.([\t]{1,})Compound..ShoulderEntity.+?(?=^([\t]{2})End)[\t]{2}End//gms') 85 | NBT_LINES_POST_AVICIDE=$(echo "$REMOVE_PARROT" | wc -l) 86 | 87 | if [[ $NBT_LINES_POST_AVICIDE -lt $NBT_LINES_PRE_AVICIDE && $(echo $REMOVE_PARROT | egrep "ShoulderEntity(Left|Right)") -eq 0 ]]; then 88 | echo ${INFO_HEADER}NBT shortened, $(echo "$CHECK_PARROTS" | wc -w) parrots removed. 89 | else 90 | echo ${ERROR_HEADER}NBT did not change, no parrots removed. 91 | exit 1 # This should not happen if the previous parrot check succeeded (unless someone decides to enter ShoulderEntity(Left|Right) on a book or smth) 92 | fi 93 | 94 | nbted -r <(echo -n "$REMOVE_PARROT") >$world/playerdata/$FORMATTED_UUID.dat # this overwrites the playerdata file 95 | echo ${INFO_HEADER}Removed parrots from $1. 96 | echo ${INFO_HEADER}You may remove $world/playerdata/$FORMATTED_UUID.dat.$backup_date.backup when ready. 97 | continue 98 | fi 99 | done 100 | 101 | if [ "$FOUND_PLAYERDATA" = false ]; then 102 | echo ${INFO_HEADER}Did not find any playerdata files for $1 \($FORMATTED_UUID\). 103 | exit 104 | fi 105 | --------------------------------------------------------------------------------